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
|
PYTHON ?= python3
|
||||||
|
|
||||||
ADMIN_API_METHODS_SIMPLE = \
|
ADMIN_API_METHODS_SIMPLE = \
|
||||||
admin.vm.List \
|
|
||||||
admin.vmclass.List \
|
admin.vmclass.List \
|
||||||
admin.Events \
|
admin.Events \
|
||||||
admin.backup.Execute \
|
admin.backup.Execute \
|
||||||
@ -17,6 +16,7 @@ ADMIN_API_METHODS_SIMPLE = \
|
|||||||
admin.label.Create \
|
admin.label.Create \
|
||||||
admin.label.Get \
|
admin.label.Get \
|
||||||
admin.label.List \
|
admin.label.List \
|
||||||
|
admin.label.Index \
|
||||||
admin.label.Remove \
|
admin.label.Remove \
|
||||||
admin.pool.Add \
|
admin.pool.Add \
|
||||||
admin.pool.Info \
|
admin.pool.Info \
|
||||||
@ -83,6 +83,8 @@ ADMIN_API_METHODS_SIMPLE = \
|
|||||||
admin.vm.tag.List \
|
admin.vm.tag.List \
|
||||||
admin.vm.tag.Remove \
|
admin.vm.tag.Remove \
|
||||||
admin.vm.tag.Set \
|
admin.vm.tag.Set \
|
||||||
|
admin.vm.volume.CloneFrom \
|
||||||
|
admin.vm.volume.CloneTo \
|
||||||
admin.vm.volume.Info \
|
admin.vm.volume.Info \
|
||||||
admin.vm.volume.List \
|
admin.vm.volume.List \
|
||||||
admin.vm.volume.ListSnapshots \
|
admin.vm.volume.ListSnapshots \
|
||||||
@ -90,10 +92,6 @@ ADMIN_API_METHODS_SIMPLE = \
|
|||||||
admin.vm.volume.Revert \
|
admin.vm.volume.Revert \
|
||||||
$(null)
|
$(null)
|
||||||
|
|
||||||
ADMIN_API_METHODS := $(ADMIN_API_METHODS_SIMPLE) \
|
|
||||||
admin.vm.volume.Import \
|
|
||||||
$(null)
|
|
||||||
|
|
||||||
ifeq ($(OS),Linux)
|
ifeq ($(OS),Linux)
|
||||||
DATADIR ?= /var/lib/qubes
|
DATADIR ?= /var/lib/qubes
|
||||||
STATEDIR ?= /var/run/qubes
|
STATEDIR ?= /var/run/qubes
|
||||||
@ -172,15 +170,26 @@ endif
|
|||||||
install qubes-rpc/qubesd-query-fast $(DESTDIR)/usr/libexec/qubes/
|
install qubes-rpc/qubesd-query-fast $(DESTDIR)/usr/libexec/qubes/
|
||||||
for method in $(ADMIN_API_METHODS_SIMPLE); do \
|
for method in $(ADMIN_API_METHODS_SIMPLE); do \
|
||||||
ln -s ../../usr/libexec/qubes/qubesd-query-fast \
|
ln -s ../../usr/libexec/qubes/qubesd-query-fast \
|
||||||
$(DESTDIR)/etc/qubes-rpc/$$method; \
|
$(DESTDIR)/etc/qubes-rpc/$$method || exit 1; \
|
||||||
done
|
done
|
||||||
install qubes-rpc/admin.vm.volume.Import $(DESTDIR)/etc/qubes-rpc/
|
install qubes-rpc/admin.vm.volume.Import $(DESTDIR)/etc/qubes-rpc/
|
||||||
for method in $(ADMIN_API_METHODS); do \
|
PYTHONPATH=.:test-packages qubes-rpc-policy/generate-admin-policy \
|
||||||
install -m 0644 qubes-rpc-policy/admin-default \
|
--destdir=$(DESTDIR)/etc/qubes-rpc/policy \
|
||||||
$(DESTDIR)/etc/qubes-rpc/policy/$$method; \
|
--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
|
done
|
||||||
install -d $(DESTDIR)/etc/qubes-rpc/policy/include
|
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/
|
$(DESTDIR)/etc/qubes-rpc/policy/include/
|
||||||
|
|
||||||
mkdir -p "$(DESTDIR)$(FILESDIR)"
|
mkdir -p "$(DESTDIR)$(FILESDIR)"
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
[run]
|
[run]
|
||||||
source = qubes
|
source = qubes, qubespolicy
|
||||||
omit = qubes/tests/*
|
omit = qubes/tests/*, qubespolicy/tests/*
|
||||||
|
@ -243,6 +243,8 @@ _man_pages_author = []
|
|||||||
man_pages = [
|
man_pages = [
|
||||||
('manpages/qubesd-query', 'qubesd-query',
|
('manpages/qubesd-query', 'qubesd-query',
|
||||||
u'Low-level qubesd interrogation tool', _man_pages_author, 1),
|
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'):
|
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,6 +11,7 @@ Policy consists of a file, which is parsed line-by-line. First matching line
|
|||||||
is used as an action.
|
is used as an action.
|
||||||
|
|
||||||
Each line consist of three values separated by white characters (space(s), tab(s)):
|
Each line consist of three values separated by white characters (space(s), tab(s)):
|
||||||
|
|
||||||
1. Source specification, which is one of:
|
1. Source specification, which is one of:
|
||||||
|
|
||||||
- domain name
|
- domain name
|
||||||
@ -30,10 +31,15 @@ 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:vm-name` - _new_ Disposable VM created from AppVM `vm-name`
|
||||||
- `$dispvm` - _new_ Disposable VM created from AppVM pointed by caller
|
- `$dispvm` - _new_ Disposable VM created from AppVM pointed by caller
|
||||||
property `default_dispvm`, which defaults to global property `default_dispvm`
|
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:
|
3. Action and optional action parameters, one of:
|
||||||
|
|
||||||
- `allow` - allow the call, without further questions; optional parameters:
|
- `allow` - allow the call, without further questions; optional parameters:
|
||||||
|
|
||||||
- `target=` - override caller provided call target -
|
- `target=` - override caller provided call target -
|
||||||
possible values are: domain name, `$dispvm` or `$dispvm:vm-name`
|
possible values are: domain name, `$dispvm` or `$dispvm:vm-name`
|
||||||
- `user=` - call the service using this user, instead of the user
|
- `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
|
- `deny` - deny the call, without further questions; no optional
|
||||||
parameters are supported
|
parameters are supported
|
||||||
- `ask` - ask the user for confirmation; optional parameters:
|
- `ask` - ask the user for confirmation; optional parameters:
|
||||||
|
|
||||||
- `target=` - override user provided call target
|
- `target=` - override user provided call target
|
||||||
- `user=` - call the service using this user, instead of the user
|
- `user=` - call the service using this user, instead of the user
|
||||||
pointed by target VM's `default_user` property
|
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
|
## This file is included from all local read-write admin.* policy files
|
||||||
## configuration_. To allow only specific action, edit specific policy file.
|
## _in default configuration_. To allow only specific action,
|
||||||
|
## edit specific policy file.
|
||||||
|
|
||||||
## Note that policy parsing stops at the first match,
|
## Note that policy parsing stops at the first match,
|
||||||
## so adding anything below "$anyvm $anyvm action" line will have no effect
|
## 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
|
## 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.
|
setter. Can raise QubesValueError if the value is invalid.
|
||||||
|
|
||||||
:param untrusted_newvalue: value to be validated
|
:param untrusted_newvalue: value to be validated
|
||||||
:return sanitized value
|
:return: sanitized value
|
||||||
:raises qubes.exc.QubesValueError
|
:raises: qubes.exc.QubesValueError
|
||||||
'''
|
'''
|
||||||
# do not treat type='str' as sufficient validation
|
# do not treat type='str' as sufficient validation
|
||||||
if self.type is not None and self.type is not str:
|
if self.type is not None and self.type is not str:
|
||||||
|
@ -41,7 +41,7 @@ class PermissionDenied(Exception):
|
|||||||
pass
|
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.
|
'''Decorator factory for methods intended to appear in API.
|
||||||
|
|
||||||
The decorated method can be called from public API using a child of
|
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 str name: qrexec rpc method name
|
||||||
:param bool no_payload: if :py:obj:`True`, will barf on non-empty payload; \
|
:param bool no_payload: if :py:obj:`True`, will barf on non-empty payload; \
|
||||||
also will not pass payload at all to the method
|
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
|
The expected function method should have one argument (other than usual
|
||||||
*self*), ``untrusted_payload``, which will contain the payload.
|
*self*), ``untrusted_payload``, which will contain the payload.
|
||||||
@ -75,11 +77,14 @@ def method(name, *, no_payload=False, endpoints=None):
|
|||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
if endpoints is None:
|
if endpoints is None:
|
||||||
func._rpcname = ((name, None),)
|
func.rpcnames = ((name, None),)
|
||||||
else:
|
else:
|
||||||
func._rpcname = tuple(
|
func.rpcnames = tuple(
|
||||||
(name.format(endpoint=endpoint), endpoint)
|
(name.format(endpoint=endpoint), endpoint)
|
||||||
for endpoint in endpoints)
|
for endpoint in endpoints)
|
||||||
|
|
||||||
|
func.classifiers = classifiers
|
||||||
|
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
@ -133,43 +138,45 @@ class AbstractQubesAPI(object):
|
|||||||
#: is this operation cancellable?
|
#: is this operation cancellable?
|
||||||
self.cancellable = False
|
self.cancellable = False
|
||||||
|
|
||||||
untrusted_candidates = []
|
candidates = list(self.list_methods(self.method))
|
||||||
for attr in dir(self):
|
|
||||||
func = getattr(self, attr)
|
|
||||||
|
|
||||||
|
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):
|
if not callable(func):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
for mname, endpoint in func._rpcname:
|
rpcnames = func.rpcnames
|
||||||
if mname != self.method:
|
|
||||||
continue
|
|
||||||
untrusted_candidates.append((func, endpoint))
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not untrusted_candidates:
|
for mname, endpoint in rpcnames:
|
||||||
raise ProtocolError('no such method: {!r}'.format(self.method))
|
if select_method is None or mname == select_method:
|
||||||
|
yield (func, mname, endpoint)
|
||||||
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
|
|
||||||
|
|
||||||
def execute(self, *, untrusted_payload):
|
def execute(self, *, untrusted_payload):
|
||||||
'''Execute management operation.
|
'''Execute management operation.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
'''
|
'''
|
||||||
handler, endpoint = self._handler
|
handler, _, endpoint = self._handler
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if endpoint is not None:
|
if endpoint is not None:
|
||||||
kwargs['endpoint'] = endpoint
|
kwargs['endpoint'] = endpoint
|
||||||
self._running_handler = asyncio.ensure_future(handler(
|
self._running_handler = asyncio.ensure_future(handler(self,
|
||||||
untrusted_payload=untrusted_payload, **kwargs))
|
untrusted_payload=untrusted_payload, **kwargs))
|
||||||
return self._running_handler
|
return self._running_handler
|
||||||
|
|
||||||
|
@ -76,7 +76,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
SOCKNAME = '/var/run/qubesd.sock'
|
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
|
@asyncio.coroutine
|
||||||
def vmclass_list(self):
|
def vmclass_list(self):
|
||||||
'''List all VM classes'''
|
'''List all VM classes'''
|
||||||
@ -89,7 +90,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
return ''.join('{}\n'.format(ep.name)
|
return ''.join('{}\n'.format(ep.name)
|
||||||
for ep in entrypoints)
|
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
|
@asyncio.coroutine
|
||||||
def vm_list(self):
|
def vm_list(self):
|
||||||
'''List all the domains'''
|
'''List all the domains'''
|
||||||
@ -106,13 +108,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
vm.get_power_state())
|
vm.get_power_state())
|
||||||
for vm in sorted(domains))
|
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
|
@asyncio.coroutine
|
||||||
def vm_property_list(self):
|
def vm_property_list(self):
|
||||||
'''List all properties on a qube'''
|
'''List all properties on a qube'''
|
||||||
return self._property_list(self.dest)
|
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
|
@asyncio.coroutine
|
||||||
def property_list(self):
|
def property_list(self):
|
||||||
'''List all global properties'''
|
'''List all global properties'''
|
||||||
@ -126,13 +130,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return ''.join('{}\n'.format(prop.__name__) for prop in properties)
|
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
|
@asyncio.coroutine
|
||||||
def vm_property_get(self):
|
def vm_property_get(self):
|
||||||
'''Get a value of one property'''
|
'''Get a value of one property'''
|
||||||
return self._property_get(self.dest)
|
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
|
@asyncio.coroutine
|
||||||
def property_get(self):
|
def property_get(self):
|
||||||
'''Get a value of one global property'''
|
'''Get a value of one global property'''
|
||||||
@ -168,14 +174,16 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
property_type,
|
property_type,
|
||||||
str(value) if value is not None else '')
|
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
|
@asyncio.coroutine
|
||||||
def vm_property_set(self, untrusted_payload):
|
def vm_property_set(self, untrusted_payload):
|
||||||
'''Set property value'''
|
'''Set property value'''
|
||||||
return self._property_set(self.dest,
|
return self._property_set(self.dest,
|
||||||
untrusted_payload=untrusted_payload)
|
untrusted_payload=untrusted_payload)
|
||||||
|
|
||||||
@qubes.api.method('admin.property.Set')
|
@qubes.api.method('admin.property.Set',
|
||||||
|
scope='global', write=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def property_set(self, untrusted_payload):
|
def property_set(self, untrusted_payload):
|
||||||
'''Set property value'''
|
'''Set property value'''
|
||||||
@ -195,13 +203,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
setattr(dest, self.arg, newvalue)
|
setattr(dest, self.arg, newvalue)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_property_help(self):
|
def vm_property_help(self):
|
||||||
'''Get help for one property'''
|
'''Get help for one property'''
|
||||||
return self._property_help(self.dest)
|
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
|
@asyncio.coroutine
|
||||||
def property_help(self):
|
def property_help(self):
|
||||||
'''Get help for one property'''
|
'''Get help for one property'''
|
||||||
@ -221,13 +231,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return qubes.utils.format_doc(doc)
|
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
|
@asyncio.coroutine
|
||||||
def vm_property_reset(self):
|
def vm_property_reset(self):
|
||||||
'''Reset a property to a default value'''
|
'''Reset a property to a default value'''
|
||||||
return self._property_reset(self.dest)
|
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
|
@asyncio.coroutine
|
||||||
def property_reset(self):
|
def property_reset(self):
|
||||||
'''Reset a property to a default value'''
|
'''Reset a property to a default value'''
|
||||||
@ -243,7 +255,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
delattr(dest, self.arg)
|
delattr(dest, self.arg)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_volume_list(self):
|
def vm_volume_list(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -251,7 +264,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
volume_names = self.fire_event_for_filter(self.dest.volumes.keys())
|
volume_names = self.fire_event_for_filter(self.dest.volumes.keys())
|
||||||
return ''.join('{}\n'.format(name) for name in volume_names)
|
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
|
@asyncio.coroutine
|
||||||
def vm_volume_info(self):
|
def vm_volume_info(self):
|
||||||
assert self.arg in self.dest.volumes.keys()
|
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
|
return ''.join('{}={}\n'.format(key, getattr(volume, key)) for key in
|
||||||
volume_properties)
|
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
|
@asyncio.coroutine
|
||||||
def vm_volume_listsnapshots(self):
|
def vm_volume_listsnapshots(self):
|
||||||
assert self.arg in self.dest.volumes.keys()
|
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)
|
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
|
@asyncio.coroutine
|
||||||
def vm_volume_revert(self, untrusted_payload):
|
def vm_volume_revert(self, untrusted_payload):
|
||||||
assert self.arg in self.dest.volumes.keys()
|
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.dest.storage.get_pool(volume).revert(revision)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_volume_clone_from(self):
|
def vm_volume_clone_from(self):
|
||||||
assert self.arg in self.dest.volumes.keys()
|
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
|
self.app.api_admin_pending_clone[token] = volume
|
||||||
return token
|
return token
|
||||||
|
|
||||||
@qubes.api.method('admin.vm.volume.CloneTo')
|
@qubes.api.method('admin.vm.volume.CloneTo',
|
||||||
|
scope='local', write=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def vm_volume_clone_to(self, untrusted_payload):
|
def vm_volume_clone_to(self, untrusted_payload):
|
||||||
assert self.arg in self.dest.volumes.keys()
|
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.dest.volumes[self.arg] = op_retval
|
||||||
self.app.save()
|
self.app.save()
|
||||||
|
|
||||||
@qubes.api.method('admin.vm.volume.Resize')
|
@qubes.api.method('admin.vm.volume.Resize',
|
||||||
|
scope='local', write=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def vm_volume_resize(self, untrusted_payload):
|
def vm_volume_resize(self, untrusted_payload):
|
||||||
assert self.arg in self.dest.volumes.keys()
|
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.dest.storage.resize(self.arg, size)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_volume_import(self):
|
def vm_volume_import(self):
|
||||||
'''Import volume data.
|
'''Import volume data.
|
||||||
@ -392,7 +414,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return '{} {}'.format(size, path)
|
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
|
@asyncio.coroutine
|
||||||
def vm_tag_list(self):
|
def vm_tag_list(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -403,7 +426,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return ''.join('{}\n'.format(tag) for tag in sorted(tags))
|
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
|
@asyncio.coroutine
|
||||||
def vm_tag_get(self):
|
def vm_tag_get(self):
|
||||||
qubes.vm.Tags.validate_tag(self.arg)
|
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'
|
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
|
@asyncio.coroutine
|
||||||
def vm_tag_set(self):
|
def vm_tag_set(self):
|
||||||
qubes.vm.Tags.validate_tag(self.arg)
|
qubes.vm.Tags.validate_tag(self.arg)
|
||||||
@ -422,7 +447,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
self.dest.tags.add(self.arg)
|
self.dest.tags.add(self.arg)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_tag_remove(self):
|
def vm_tag_remove(self):
|
||||||
qubes.vm.Tags.validate_tag(self.arg)
|
qubes.vm.Tags.validate_tag(self.arg)
|
||||||
@ -435,7 +461,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
raise qubes.exc.QubesTagNotFoundError(self.dest, self.arg)
|
raise qubes.exc.QubesTagNotFoundError(self.dest, self.arg)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def pool_list(self):
|
def pool_list(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -445,7 +472,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return ''.join('{}\n'.format(pool) for pool in pools)
|
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
|
@asyncio.coroutine
|
||||||
def pool_listdrivers(self):
|
def pool_listdrivers(self):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -458,7 +486,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
' '.join(qubes.storage.driver_parameters(driver)))
|
' '.join(qubes.storage.driver_parameters(driver)))
|
||||||
for driver in drivers)
|
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
|
@asyncio.coroutine
|
||||||
def pool_info(self):
|
def pool_info(self):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -471,7 +500,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
return ''.join('{}={}\n'.format(prop, val)
|
return ''.join('{}={}\n'.format(prop, val)
|
||||||
for prop, val in sorted(pool.config.items()))
|
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
|
@asyncio.coroutine
|
||||||
def pool_add(self, untrusted_payload):
|
def pool_add(self, untrusted_payload):
|
||||||
assert self.dest.name == 'dom0'
|
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.add_pool(name=pool_name, driver=self.arg, **pool_config)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def pool_remove(self):
|
def pool_remove(self):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -517,7 +548,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
self.app.remove_pool(self.arg)
|
self.app.remove_pool(self.arg)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def label_list(self):
|
def label_list(self):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -527,7 +559,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return ''.join('{}\n'.format(label.name) for label in labels)
|
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
|
@asyncio.coroutine
|
||||||
def label_get(self):
|
def label_get(self):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -541,7 +574,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return label.color
|
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
|
@asyncio.coroutine
|
||||||
def label_index(self):
|
def label_index(self):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -555,7 +589,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
return str(label.index)
|
return str(label.index)
|
||||||
|
|
||||||
@qubes.api.method('admin.label.Create')
|
@qubes.api.method('admin.label.Create',
|
||||||
|
scope='global', write=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def label_create(self, untrusted_payload):
|
def label_create(self, untrusted_payload):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -591,7 +626,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
self.app.labels[new_index] = label
|
self.app.labels[new_index] = label
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def label_remove(self):
|
def label_remove(self):
|
||||||
assert self.dest.name == 'dom0'
|
assert self.dest.name == 'dom0'
|
||||||
@ -613,7 +649,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
del self.app.labels[label.index]
|
del self.app.labels[label.index]
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_start(self):
|
def vm_start(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -625,35 +662,40 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
raise qubes.exc.QubesException('Start failed: ' + str(e))
|
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
|
@asyncio.coroutine
|
||||||
def vm_shutdown(self):
|
def vm_shutdown(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
self.fire_event_for_permission()
|
self.fire_event_for_permission()
|
||||||
yield from self.dest.shutdown()
|
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
|
@asyncio.coroutine
|
||||||
def vm_pause(self):
|
def vm_pause(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
self.fire_event_for_permission()
|
self.fire_event_for_permission()
|
||||||
yield from self.dest.pause()
|
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
|
@asyncio.coroutine
|
||||||
def vm_unpause(self):
|
def vm_unpause(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
self.fire_event_for_permission()
|
self.fire_event_for_permission()
|
||||||
yield from self.dest.unpause()
|
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
|
@asyncio.coroutine
|
||||||
def vm_kill(self):
|
def vm_kill(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
self.fire_event_for_permission()
|
self.fire_event_for_permission()
|
||||||
yield from self.dest.kill()
|
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
|
@asyncio.coroutine
|
||||||
def events(self):
|
def events(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -694,14 +736,16 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
else:
|
else:
|
||||||
self.dest.remove_handler('*', dispatcher.vm_handler)
|
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
|
@asyncio.coroutine
|
||||||
def vm_feature_list(self):
|
def vm_feature_list(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
features = self.fire_event_for_filter(self.dest.features.keys())
|
features = self.fire_event_for_filter(self.dest.features.keys())
|
||||||
return ''.join('{}\n'.format(feature) for feature in features)
|
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
|
@asyncio.coroutine
|
||||||
def vm_feature_get(self):
|
def vm_feature_get(self):
|
||||||
# validation of self.arg done by qrexec-policy is enough
|
# 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)
|
raise qubes.exc.QubesFeatureNotFoundError(self.dest, self.arg)
|
||||||
return value
|
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
|
@asyncio.coroutine
|
||||||
def vm_feature_checkwithtemplate(self):
|
def vm_feature_checkwithtemplate(self):
|
||||||
# validation of self.arg done by qrexec-policy is enough
|
# 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)
|
raise qubes.exc.QubesFeatureNotFoundError(self.dest, self.arg)
|
||||||
return value
|
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
|
@asyncio.coroutine
|
||||||
def vm_feature_remove(self):
|
def vm_feature_remove(self):
|
||||||
# validation of self.arg done by qrexec-policy is enough
|
# 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)
|
raise qubes.exc.QubesFeatureNotFoundError(self.dest, self.arg)
|
||||||
self.app.save()
|
self.app.save()
|
||||||
|
|
||||||
@qubes.api.method('admin.vm.feature.Set')
|
@qubes.api.method('admin.vm.feature.Set',
|
||||||
|
scope='local', write=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def vm_feature_set(self, untrusted_payload):
|
def vm_feature_set(self, untrusted_payload):
|
||||||
# validation of self.arg done by qrexec-policy is enough
|
# validation of self.arg done by qrexec-policy is enough
|
||||||
@ -749,14 +796,16 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
self.app.save()
|
self.app.save()
|
||||||
|
|
||||||
@qubes.api.method('admin.vm.Create.{endpoint}', endpoints=(ep.name
|
@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
|
@asyncio.coroutine
|
||||||
def vm_create(self, endpoint, untrusted_payload=None):
|
def vm_create(self, endpoint, untrusted_payload=None):
|
||||||
return self._vm_create(endpoint, allow_pool=False,
|
return self._vm_create(endpoint, allow_pool=False,
|
||||||
untrusted_payload=untrusted_payload)
|
untrusted_payload=untrusted_payload)
|
||||||
|
|
||||||
@qubes.api.method('admin.vm.CreateInPool.{endpoint}', endpoints=(ep.name
|
@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
|
@asyncio.coroutine
|
||||||
def vm_create_in_pool(self, endpoint, untrusted_payload=None):
|
def vm_create_in_pool(self, endpoint, untrusted_payload=None):
|
||||||
return self._vm_create(endpoint, allow_pool=True,
|
return self._vm_create(endpoint, allow_pool=True,
|
||||||
@ -846,7 +895,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
raise
|
raise
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_remove(self):
|
def vm_remove(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -867,7 +917,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
@qubes.api.method('admin.vm.device.{endpoint}.Available', endpoints=(ep.name
|
@qubes.api.method('admin.vm.device.{endpoint}.Available', endpoints=(ep.name
|
||||||
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||||
no_payload=True)
|
no_payload=True,
|
||||||
|
scope='local', read=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def vm_device_available(self, endpoint):
|
def vm_device_available(self, endpoint):
|
||||||
devclass = endpoint
|
devclass = endpoint
|
||||||
@ -901,7 +952,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
@qubes.api.method('admin.vm.device.{endpoint}.List', endpoints=(ep.name
|
@qubes.api.method('admin.vm.device.{endpoint}.List', endpoints=(ep.name
|
||||||
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||||
no_payload=True)
|
no_payload=True,
|
||||||
|
scope='local', read=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def vm_device_list(self, endpoint):
|
def vm_device_list(self, endpoint):
|
||||||
devclass = endpoint
|
devclass = endpoint
|
||||||
@ -932,8 +984,12 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
return ''.join('{} {}\n'.format(ident, dev_info[ident])
|
return ''.join('{} {}\n'.format(ident, dev_info[ident])
|
||||||
for ident in sorted(dev_info))
|
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
|
@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
|
@asyncio.coroutine
|
||||||
def vm_device_attach(self, endpoint, untrusted_payload):
|
def vm_device_attach(self, endpoint, untrusted_payload):
|
||||||
devclass = endpoint
|
devclass = endpoint
|
||||||
@ -972,9 +1028,13 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
yield from self.dest.devices[devclass].attach(assignment)
|
yield from self.dest.devices[devclass].attach(assignment)
|
||||||
self.app.save()
|
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
|
@qubes.api.method('admin.vm.device.{endpoint}.Detach', endpoints=(ep.name
|
||||||
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||||
no_payload=True)
|
no_payload=True,
|
||||||
|
scope='local', write=True, execute=True)
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def vm_device_detach(self, endpoint):
|
def vm_device_detach(self, endpoint):
|
||||||
devclass = endpoint
|
devclass = endpoint
|
||||||
@ -994,7 +1054,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
yield from self.dest.devices[devclass].detach(assignment)
|
yield from self.dest.devices[devclass].detach(assignment)
|
||||||
self.app.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_firewall_get(self):
|
def vm_firewall_get(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -1004,7 +1065,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
return ''.join('{}\n'.format(rule.api_rule)
|
return ''.join('{}\n'.format(rule.api_rule)
|
||||||
for rule in self.dest.firewall.rules)
|
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
|
@asyncio.coroutine
|
||||||
def vm_firewall_set(self, untrusted_payload):
|
def vm_firewall_set(self, untrusted_payload):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
@ -1020,7 +1082,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
|||||||
self.dest.firewall.rules = rules
|
self.dest.firewall.rules = rules
|
||||||
self.dest.firewall.save()
|
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
|
@asyncio.coroutine
|
||||||
def vm_firewall_reload(self):
|
def vm_firewall_reload(self):
|
||||||
assert not self.arg
|
assert not self.arg
|
||||||
|
@ -119,6 +119,7 @@ class BackupHeader(object):
|
|||||||
|
|
||||||
:param untrusted_header_text: header content
|
:param untrusted_header_text: header content
|
||||||
:type untrusted_header_text: basestring
|
:type untrusted_header_text: basestring
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
This function may be exposed to not yet verified header,
|
This function may be exposed to not yet verified header,
|
||||||
so is security critical.
|
so is security critical.
|
||||||
|
@ -249,7 +249,7 @@ class OptionsCheckVisitor(docutils.nodes.SparseNodeVisitor):
|
|||||||
While the documentation talks about a
|
While the documentation talks about a
|
||||||
'SparseNodeVisitor.depart_document()' function, this function does
|
'SparseNodeVisitor.depart_document()' function, this function does
|
||||||
not exists. (For details see implementation of
|
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.
|
manually call this.
|
||||||
'''
|
'''
|
||||||
if ignored_options is None:
|
if ignored_options is None:
|
||||||
@ -316,7 +316,7 @@ class CommandCheckVisitor(docutils.nodes.SparseNodeVisitor):
|
|||||||
While the documentation talks about a
|
While the documentation talks about a
|
||||||
'SparseNodeVisitor.depart_document()' function, this function does
|
'SparseNodeVisitor.depart_document()' function, this function does
|
||||||
not exists. (For details see implementation of
|
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.
|
manually call this.
|
||||||
'''
|
'''
|
||||||
if self.sub_commands:
|
if self.sub_commands:
|
||||||
|
@ -304,10 +304,12 @@ class QubesArgumentParser(argparse.ArgumentParser):
|
|||||||
:param bool want_force_root: add ``--force-root`` option
|
:param bool want_force_root: add ``--force-root`` option
|
||||||
:param mixed vmname_nargs: The number of ``VMNAME`` arguments that should be
|
:param mixed vmname_nargs: The number of ``VMNAME`` arguments that should be
|
||||||
consumed. Values include:
|
consumed. Values include:
|
||||||
|
|
||||||
- N (an integer) consumes N arguments (and produces a list)
|
- N (an integer) consumes N arguments (and produces a list)
|
||||||
- '?' consumes zero or one arguments
|
- '?' consumes zero or one arguments
|
||||||
- '*' consumes zero or more arguments (and produces a list)
|
- '*' consumes zero or more arguments (and produces a list)
|
||||||
- '+' consumes one or more arguments (and produces a list)
|
- '+' consumes one or more arguments (and produces a list)
|
||||||
|
|
||||||
*kwargs* are passed to :py:class:`argparser.ArgumentParser`.
|
*kwargs* are passed to :py:class:`argparser.ArgumentParser`.
|
||||||
|
|
||||||
Currenty supported options:
|
Currenty supported options:
|
||||||
|
@ -594,7 +594,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def block_devices(self):
|
def block_devices(self):
|
||||||
''' Return all :py:class:`qubes.storage.BlockDevice`s for current domain
|
''' Return all :py:class:`qubes.storage.BlockDevice` for current domain
|
||||||
for serialization in the libvirt XML template as <disk>.
|
for serialization in the libvirt XML template as <disk>.
|
||||||
'''
|
'''
|
||||||
for v in self.volumes.values():
|
for v in self.volumes.values():
|
||||||
|
@ -66,6 +66,8 @@ def verify_target_value(system_info, value):
|
|||||||
'''
|
'''
|
||||||
if value == '$dispvm':
|
if value == '$dispvm':
|
||||||
return True
|
return True
|
||||||
|
elif value == '$adminvm':
|
||||||
|
return True
|
||||||
elif value.startswith('$dispvm:'):
|
elif value.startswith('$dispvm:'):
|
||||||
dispvm_base = value.split(':', 1)[1]
|
dispvm_base = value.split(':', 1)[1]
|
||||||
if dispvm_base not in system_info['domains']:
|
if dispvm_base not in system_info['domains']:
|
||||||
@ -93,6 +95,8 @@ def verify_special_value(value, for_target=True):
|
|||||||
return True
|
return True
|
||||||
elif value == '$anyvm':
|
elif value == '$anyvm':
|
||||||
return True
|
return True
|
||||||
|
elif value == '$adminvm':
|
||||||
|
return True
|
||||||
elif value.startswith('$dispvm:') and for_target:
|
elif value.startswith('$dispvm:') and for_target:
|
||||||
return True
|
return True
|
||||||
elif value == '$dispvm' and for_target:
|
elif value == '$dispvm' and for_target:
|
||||||
@ -121,11 +125,11 @@ class PolicyRule(object):
|
|||||||
self.filename = filename
|
self.filename = filename
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.source, self.target, self.full_action = line.split()
|
self.source, self.target, self.full_action = line.split(maxsplit=2)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise PolicySyntaxError(filename, lineno, 'wrong number of fields')
|
raise PolicySyntaxError(filename, lineno, 'wrong number of fields')
|
||||||
|
|
||||||
(action, *params) = self.full_action.split(',')
|
(action, *params) = self.full_action.replace(',', ' ').split()
|
||||||
try:
|
try:
|
||||||
self.action = Action[action]
|
self.action = Action[action]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -184,8 +188,9 @@ class PolicyRule(object):
|
|||||||
'allow action for $default rule must specify target= option')
|
'allow action for $default rule must specify target= option')
|
||||||
|
|
||||||
if self.override_target is not None:
|
if self.override_target is not None:
|
||||||
if self.override_target.startswith('$') and not \
|
if self.override_target.startswith('$') and \
|
||||||
self.override_target.startswith('$dispvm'):
|
not self.override_target.startswith('$dispvm') and \
|
||||||
|
self.override_target != '$adminvm':
|
||||||
raise PolicySyntaxError(filename, lineno,
|
raise PolicySyntaxError(filename, lineno,
|
||||||
'target= option needs to name specific target')
|
'target= option needs to name specific target')
|
||||||
|
|
||||||
@ -216,11 +221,19 @@ class PolicyRule(object):
|
|||||||
if not verify_target_value(system_info, value):
|
if not verify_target_value(system_info, value):
|
||||||
return False
|
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
|
# allow any _valid_, non-dom0 target
|
||||||
if policy_value == '$anyvm':
|
if policy_value == '$anyvm':
|
||||||
return value != 'dom0'
|
return value != '$adminvm'
|
||||||
|
|
||||||
# exact match, including $dispvm*
|
# exact match, including $dispvm* and $adminvm
|
||||||
if value == policy_value:
|
if value == policy_value:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -229,6 +242,11 @@ class PolicyRule(object):
|
|||||||
if value.startswith('$dispvm'):
|
if value.startswith('$dispvm'):
|
||||||
return False
|
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
|
# at this point, value name a specific target
|
||||||
domain_info = system_info['domains'][value]
|
domain_info = system_info['domains'][value]
|
||||||
|
|
||||||
@ -293,6 +311,8 @@ class PolicyRule(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
# TODO log a warning?
|
# TODO log a warning?
|
||||||
pass
|
pass
|
||||||
|
elif self.target == '$adminvm':
|
||||||
|
yield self.target
|
||||||
elif self.target == '$dispvm':
|
elif self.target == '$dispvm':
|
||||||
yield self.target
|
yield self.target
|
||||||
else:
|
else:
|
||||||
@ -372,12 +392,14 @@ class PolicyAction(object):
|
|||||||
def execute(self, caller_ident):
|
def execute(self, caller_ident):
|
||||||
''' Execute allowed service call
|
''' Execute allowed service call
|
||||||
|
|
||||||
:param caller_ident: Service caller ident (`process_ident,source_name,
|
:param caller_ident: Service caller ident
|
||||||
source_id`)
|
(`process_ident,source_name, source_id`)
|
||||||
'''
|
'''
|
||||||
assert self.action == Action.allow
|
assert self.action == Action.allow
|
||||||
assert self.target is not None
|
assert self.target is not None
|
||||||
|
|
||||||
|
if self.target == '$adminvm':
|
||||||
|
self.target = 'dom0'
|
||||||
if self.target == 'dom0':
|
if self.target == 'dom0':
|
||||||
cmd = '{multiplexer} {service} {source} {original_target}'.format(
|
cmd = '{multiplexer} {service} {source} {original_target}'.format(
|
||||||
multiplexer=QUBES_RPC_MULTIPLEXER_PATH,
|
multiplexer=QUBES_RPC_MULTIPLEXER_PATH,
|
||||||
@ -451,17 +473,20 @@ class Policy(object):
|
|||||||
>>> policy = Policy('some-service')
|
>>> policy = Policy('some-service')
|
||||||
>>> action = policy.evaluate(system_info, 'source-name', 'target-name')
|
>>> action = policy.evaluate(system_info, 'source-name', 'target-name')
|
||||||
>>> if action.action == Action.ask:
|
>>> 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.handle_user_response(response, target_chosen_by_user)
|
||||||
>>> action.execute('process-ident')
|
>>> action.execute('process-ident')
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, service):
|
def __init__(self, service, policy_dir=POLICY_DIR):
|
||||||
policy_file = os.path.join(POLICY_DIR, service)
|
policy_file = os.path.join(policy_dir, service)
|
||||||
if not os.path.exists(policy_file):
|
if not os.path.exists(policy_file):
|
||||||
# fallback to policy without specific argument set (if any)
|
# 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
|
#: service name
|
||||||
self.service = service
|
self.service = service
|
||||||
@ -493,7 +518,7 @@ class Policy(object):
|
|||||||
include_path = line.split(':', 1)[1]
|
include_path = line.split(':', 1)[1]
|
||||||
# os.path.join will leave include_path unchanged if it's
|
# os.path.join will leave include_path unchanged if it's
|
||||||
# already absolute
|
# 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)
|
self.load_policy_file(include_path)
|
||||||
else:
|
else:
|
||||||
self.policy_rules.append(PolicyRule(line, path, lineno))
|
self.policy_rules.append(PolicyRule(line, path, lineno))
|
||||||
@ -582,6 +607,15 @@ class Policy(object):
|
|||||||
'policy define \'allow\' action at {}:{} but no target is '
|
'policy define \'allow\' action at {}:{} but no target is '
|
||||||
'specified by caller or policy'.format(
|
'specified by caller or policy'.format(
|
||||||
rule.filename, rule.lineno))
|
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,
|
return PolicyAction(self.service, source,
|
||||||
actual_target, rule, target)
|
actual_target, rule, target)
|
||||||
else:
|
else:
|
||||||
@ -634,7 +668,7 @@ def get_system_info():
|
|||||||
data is nested dict structure with this structure:
|
data is nested dict structure with this structure:
|
||||||
|
|
||||||
- domains:
|
- domains:
|
||||||
- <domain name>:
|
- `<domain name>`:
|
||||||
- tags: list of tags
|
- tags: list of tags
|
||||||
- type: domain type
|
- type: domain type
|
||||||
- dispvm_allowed: should DispVM based on this VM be allowed
|
- dispvm_allowed: should DispVM based on this VM be allowed
|
||||||
|
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 = {
|
system_info = {
|
||||||
'domains': {
|
'domains': {
|
||||||
'dom0': {
|
'dom0': {
|
||||||
'tags': [],
|
'tags': ['dom0-tag'],
|
||||||
'type': 'AdminVM',
|
'type': 'AdminVM',
|
||||||
'default_dispvm': 'default-dvm',
|
'default_dispvm': 'default-dvm',
|
||||||
'dispvm_allowed': False,
|
'dispvm_allowed': False,
|
||||||
@ -102,6 +102,8 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
|||||||
qubespolicy.verify_target_value(system_info, 'test-template'))
|
qubespolicy.verify_target_value(system_info, 'test-template'))
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
qubespolicy.verify_target_value(system_info, 'test-standalone'))
|
qubespolicy.verify_target_value(system_info, 'test-standalone'))
|
||||||
|
self.assertTrue(
|
||||||
|
qubespolicy.verify_target_value(system_info, '$adminvm'))
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
qubespolicy.verify_target_value(system_info, 'no-such-vm'))
|
qubespolicy.verify_target_value(system_info, 'no-such-vm'))
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
@ -127,6 +129,8 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
|||||||
for_target=False))
|
for_target=False))
|
||||||
self.assertTrue(qubespolicy.verify_special_value('$type:AppVM',
|
self.assertTrue(qubespolicy.verify_special_value('$type:AppVM',
|
||||||
for_target=False))
|
for_target=False))
|
||||||
|
self.assertTrue(qubespolicy.verify_special_value('$adminvm',
|
||||||
|
for_target=False))
|
||||||
self.assertFalse(qubespolicy.verify_special_value('$default',
|
self.assertFalse(qubespolicy.verify_special_value('$default',
|
||||||
for_target=False))
|
for_target=False))
|
||||||
self.assertFalse(qubespolicy.verify_special_value('$dispvm',
|
self.assertFalse(qubespolicy.verify_special_value('$dispvm',
|
||||||
@ -155,6 +159,7 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
|||||||
self.assertIsNone(line.default_target)
|
self.assertIsNone(line.default_target)
|
||||||
|
|
||||||
def test_021_line_simple(self):
|
def test_021_line_simple(self):
|
||||||
|
# also check spaces in action field
|
||||||
line = qubespolicy.PolicyRule(
|
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)
|
'filename', 12)
|
||||||
@ -196,6 +201,20 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
|||||||
self.assertIsNone(line.override_user)
|
self.assertIsNone(line.override_user)
|
||||||
self.assertEqual(line.default_target, 'test-vm1')
|
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):
|
def test_030_line_invalid(self):
|
||||||
invalid_lines = [
|
invalid_lines = [
|
||||||
'$dispvm $default allow', # $dispvm can't be a source
|
'$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,
|
self.assertTrue(is_match_single(system_info,
|
||||||
'$anyvm', '$dispvm:default-dvm'))
|
'$anyvm', '$dispvm:default-dvm'))
|
||||||
self.assertTrue(is_match_single(system_info, '$dispvm', '$dispvm'))
|
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, 'dom0', 'dom0'))
|
||||||
self.assertTrue(is_match_single(system_info,
|
self.assertTrue(is_match_single(system_info,
|
||||||
'$dispvm:default-dvm', '$dispvm:default-dvm'))
|
'$dispvm:default-dvm', '$dispvm:default-dvm'))
|
||||||
@ -253,6 +275,15 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
|||||||
self.assertFalse(is_match_single(system_info,
|
self.assertFalse(is_match_single(system_info,
|
||||||
'$dispvm:test-vm1', '$dispvm:test-vm1'))
|
'$dispvm:test-vm1', '$dispvm:test-vm1'))
|
||||||
self.assertFalse(is_match_single(system_info, '$anyvm', 'dom0'))
|
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, '$tag:tag1', 'dom0'))
|
||||||
self.assertFalse(is_match_single(system_info, '$anyvm', '$tag:tag1'))
|
self.assertFalse(is_match_single(system_info, '$anyvm', '$tag:tag1'))
|
||||||
self.assertFalse(is_match_single(system_info, '$anyvm', '$type:AppVM'))
|
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'),
|
line.expand_override_target(system_info, 'test-no-dvm'),
|
||||||
'dom0')
|
'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):
|
class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
||||||
def test_000_init(self):
|
def test_000_init(self):
|
||||||
@ -485,7 +523,6 @@ class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
|||||||
[unittest.mock.call('test-vm2', 'internal.vm.Start')])
|
[unittest.mock.call('test-vm2', 'internal.vm.Start')])
|
||||||
self.assertEqual(mock_subprocess.mock_calls, [])
|
self.assertEqual(mock_subprocess.mock_calls, [])
|
||||||
|
|
||||||
@unittest.mock.patch('qubespolicy.POLICY_DIR', tmp_policy_dir)
|
|
||||||
class TC_20_Policy(qubes.tests.QubesTestCase):
|
class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -505,7 +542,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
|||||||
f.write('test-vm2 test-vm3 ask\n')
|
f.write('test-vm2 test-vm3 ask\n')
|
||||||
f.write(' # comment \n')
|
f.write(' # comment \n')
|
||||||
f.write('$anyvm $anyvm ask\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(policy.service, 'test.service')
|
||||||
self.assertEqual(len(policy.policy_rules), 3)
|
self.assertEqual(len(policy.policy_rules), 3)
|
||||||
self.assertEqual(policy.policy_rules[0].source, 'test-vm1')
|
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):
|
def test_001_not_existent(self):
|
||||||
with self.assertRaises(qubespolicy.AccessDenied):
|
with self.assertRaises(qubespolicy.AccessDenied):
|
||||||
qubespolicy.Policy('no-such.service')
|
qubespolicy.Policy('no-such.service', tmp_policy_dir)
|
||||||
|
|
||||||
def test_002_include(self):
|
def test_002_include(self):
|
||||||
with open(os.path.join(tmp_policy_dir, 'test.service'), 'w') as f:
|
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')
|
f.write('$anyvm $anyvm deny\n')
|
||||||
with open(os.path.join(tmp_policy_dir, 'test.service2'), 'w') as f:
|
with open(os.path.join(tmp_policy_dir, 'test.service2'), 'w') as f:
|
||||||
f.write('test-vm3 $default allow,target=test-vm2\n')
|
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(policy.service, 'test.service')
|
||||||
self.assertEqual(len(policy.policy_rules), 3)
|
self.assertEqual(len(policy.policy_rules), 3)
|
||||||
self.assertEqual(policy.policy_rules[0].source, 'test-vm1')
|
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('test-vm2 $tag:tag2 allow\n')
|
||||||
f.write('$type:AppVM $default allow,target=test-vm3\n')
|
f.write('$type:AppVM $default allow,target=test-vm3\n')
|
||||||
f.write('$tag:tag1 $type:AppVM allow\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(
|
self.assertEqual(policy.find_matching_rule(
|
||||||
system_info, 'test-vm1', 'test-vm2'), policy.policy_rules[0])
|
system_info, 'test-vm1', 'test-vm2'), policy.policy_rules[0])
|
||||||
self.assertEqual(policy.find_matching_rule(
|
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('$tag:tag1 $type:AppVM allow\n')
|
||||||
f.write('test-no-dvm $dispvm allow\n')
|
f.write('test-no-dvm $dispvm allow\n')
|
||||||
f.write('test-standalone $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,
|
self.assertCountEqual(policy.collect_targets_for_ask(system_info,
|
||||||
'test-vm1'), ['test-vm1', 'test-vm2', 'test-vm3',
|
'test-vm1'), ['test-vm1', 'test-vm2', 'test-vm3',
|
||||||
'$dispvm: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:
|
with open(os.path.join(tmp_policy_dir, 'test.service'), 'w') as f:
|
||||||
f.write('test-vm1 test-vm2 allow\n')
|
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')
|
action = policy.evaluate(system_info, 'test-vm1', 'test-vm2')
|
||||||
self.assertEqual(action.rule, policy.policy_rules[0])
|
self.assertEqual(action.rule, policy.policy_rules[0])
|
||||||
self.assertEqual(action.action, qubespolicy.Action.allow)
|
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('$tag:tag2 $anyvm allow\n')
|
||||||
f.write('test-vm3 $anyvm deny\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')
|
action = policy.evaluate(system_info, 'test-vm1', '$default')
|
||||||
self.assertEqual(action.rule, policy.policy_rules[1])
|
self.assertEqual(action.rule, policy.policy_rules[1])
|
||||||
self.assertEqual(action.action, qubespolicy.Action.allow)
|
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('$tag:tag2 $anyvm allow\n')
|
||||||
f.write('test-vm3 $anyvm deny\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')
|
action = policy.evaluate(system_info, 'test-standalone', 'test-vm2')
|
||||||
self.assertEqual(action.rule, policy.policy_rules[2])
|
self.assertEqual(action.rule, policy.policy_rules[2])
|
||||||
self.assertEqual(action.action, qubespolicy.Action.ask)
|
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('$tag:tag2 $anyvm allow\n')
|
||||||
f.write('test-vm3 $anyvm deny\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')
|
action = policy.evaluate(system_info, 'test-standalone', 'test-vm3')
|
||||||
self.assertEqual(action.rule, policy.policy_rules[3])
|
self.assertEqual(action.rule, policy.policy_rules[3])
|
||||||
self.assertEqual(action.action, qubespolicy.Action.ask)
|
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',
|
'default-dvm', '$dispvm:default-dvm', 'test-invalid-dvm',
|
||||||
'test-no-dvm', 'test-template', 'test-standalone'])
|
'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):
|
class TC_30_Misc(qubes.tests.QubesTestCase):
|
||||||
@unittest.mock.patch('socket.socket')
|
@unittest.mock.patch('socket.socket')
|
||||||
|
@ -213,7 +213,9 @@ fi
|
|||||||
/usr/bin/qubesd*
|
/usr/bin/qubesd*
|
||||||
/usr/bin/qrexec-policy
|
/usr/bin/qrexec-policy
|
||||||
/usr/bin/qrexec-policy-agent
|
/usr/bin/qrexec-policy-agent
|
||||||
|
/usr/bin/qrexec-policy-graph
|
||||||
|
|
||||||
|
%{_mandir}/man1/qrexec-policy-graph.1*
|
||||||
%{_mandir}/man1/qubes*.1*
|
%{_mandir}/man1/qubes*.1*
|
||||||
|
|
||||||
%dir %{python3_sitelib}/qubes-*.egg-info
|
%dir %{python3_sitelib}/qubes-*.egg-info
|
||||||
@ -372,6 +374,7 @@ fi
|
|||||||
%{python3_sitelib}/qubespolicy/gtkhelpers.py
|
%{python3_sitelib}/qubespolicy/gtkhelpers.py
|
||||||
%{python3_sitelib}/qubespolicy/rpcconfirmation.py
|
%{python3_sitelib}/qubespolicy/rpcconfirmation.py
|
||||||
%{python3_sitelib}/qubespolicy/utils.py
|
%{python3_sitelib}/qubespolicy/utils.py
|
||||||
|
%{python3_sitelib}/qubespolicy/graph.py
|
||||||
|
|
||||||
%dir %{python3_sitelib}/qubespolicy/tests
|
%dir %{python3_sitelib}/qubespolicy/tests
|
||||||
%dir %{python3_sitelib}/qubespolicy/tests/__pycache__
|
%dir %{python3_sitelib}/qubespolicy/tests/__pycache__
|
||||||
@ -410,7 +413,10 @@ fi
|
|||||||
/etc/xen/scripts/block-origin
|
/etc/xen/scripts/block-origin
|
||||||
/etc/xen/scripts/vif-route-qubes
|
/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/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.FeaturesRequest
|
||||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.Filecopy
|
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.Filecopy
|
||||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.GetImageRGBA
|
%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()) + [
|
'console_scripts': list(get_console_scripts()) + [
|
||||||
'qrexec-policy = qubespolicy.cli:main',
|
'qrexec-policy = qubespolicy.cli:main',
|
||||||
'qrexec-policy-agent = qubespolicy.agent:main',
|
'qrexec-policy-agent = qubespolicy.agent:main',
|
||||||
|
'qrexec-policy-graph = qubespolicy.graph:main',
|
||||||
],
|
],
|
||||||
'qubes.vm': [
|
'qubes.vm': [
|
||||||
'AppVM = qubes.vm.appvm:AppVM',
|
'AppVM = qubes.vm.appvm:AppVM',
|
||||||
|
Loading…
Reference in New Issue
Block a user