doc: document features, qvm-features-request and services
Fixes QubesOS/qubes-issues#2829
This commit is contained in:
parent
d1f5cb5d15
commit
295705a708
@ -16,6 +16,7 @@ manpages and API documentation. For primary user documentation, see
|
||||
qubes
|
||||
qubes-vm/index
|
||||
qubes-events
|
||||
qubes-features
|
||||
qubes-storage
|
||||
qubes-exc
|
||||
qubes-ext
|
||||
|
193
doc/qubes-features.rst
Normal file
193
doc/qubes-features.rst
Normal file
@ -0,0 +1,193 @@
|
||||
:py:class:`qubes.vm.Features` - Qubes VM features, services
|
||||
============================================================
|
||||
|
||||
Features are generic mechanism for storing key-value pairs attached to a
|
||||
VM. The primary use case for them is data storage for extensions (you can think
|
||||
of them as more flexible properties, defined by extensions), but some are also
|
||||
used in the qubes core itself. There is no definite list of supported features,
|
||||
each extension can set their own and there is no requirement of registration,
|
||||
but :program:`qvm-features` man page contains well known ones.
|
||||
In addition, there is a mechanism for VM request setting a feature. This is
|
||||
useful for extensions to discover if its VM part is present.
|
||||
|
||||
Features can have three distinct values: no value (not present in mapping,
|
||||
which is closest thing to :py:obj:`None`), empty string (which is
|
||||
interpreted as :py:obj:`False`) and non-empty string, which is
|
||||
:py:obj:`True`. Anything assigned to the mapping is coerced to strings,
|
||||
however if you assign instances of :py:class:`bool`, they are converted as
|
||||
described above. Be aware that assigning the number `0` (which is considered
|
||||
false in Python) will result in string `'0'`, which is considered true.
|
||||
|
||||
:py:class:`qubes.vm.Features` inherits from :py:class:`dict`, so provide all the
|
||||
standard functions to get, list and set values. Additionally provide helper
|
||||
functions to check if given feature is set on the VM and default to the value
|
||||
on the VM's template or netvm. This is useful for features which nature is
|
||||
inherited from other VMs, like "is package X is installed" or "is VM behind a
|
||||
VPN".
|
||||
|
||||
Example usage of features in extension:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import qubes.exc
|
||||
import qubes.ext
|
||||
|
||||
class ExampleExtension(qubes.ext.Extension):
|
||||
@qubes.ext.handler('domain-pre-start')
|
||||
def on_domain_start(self, vm, event, **kwargs):
|
||||
if vm.features.get('do-not-start', False):
|
||||
raise qubes.exc.QubesVMError(vm,
|
||||
'Start prohibited because of do-not-start feature')
|
||||
|
||||
if vm.features.check_with_template('something-installed', False):
|
||||
# do something
|
||||
|
||||
The above extension does two things:
|
||||
|
||||
- prevent starting a qube with ``do-not-start`` feature set
|
||||
- do something when ``something-installed`` feature is set on the qube, or its template
|
||||
|
||||
|
||||
qvm-features-request, qubes.PostInstall service
|
||||
------------------------------------------------
|
||||
|
||||
When some package in the VM want to request feature to be set (aka advertise
|
||||
support for it), it should place a shell script in ``/etc/qubes/post-install.d``.
|
||||
This script should call :program:`qvm-features-request` with ``FEATURE=VALUE`` pair(s) as
|
||||
arguments to request those features. It is recommended to use very simple
|
||||
values here (for example ``1``). The script should be named in form
|
||||
``XX-package-name.sh`` where ``XX`` is two-digits number below 90 and
|
||||
``package-name`` is unique name specific to this package (preferably actual
|
||||
package name). The script needs executable bit set.
|
||||
|
||||
``qubes.PostInstall`` service will call all those scripts after any package
|
||||
installation and also after initial template installation.
|
||||
This way package have a chance to report to dom0 if any feature is
|
||||
added/removed.
|
||||
|
||||
The features flow to dom0 according to the diagram below. Important part is
|
||||
that qubes core :py:class:`qubes.ext.Extension` is responsible for handling such request in
|
||||
``features-request`` event handler. If no extension handles given feature request,
|
||||
it will be ignored. The extension should carefuly validate requested
|
||||
features (ignoring those not recognized - may be for another extension) and
|
||||
only then set appropriate value on VM object
|
||||
(:py:attr:`qubes.vm.BaseVM.features`). It is recommended to make the
|
||||
verification code as bulletproof as possible (for example allow only specific
|
||||
simple values, instead of complex structures), because feature requests
|
||||
come from untrusted sources. The features actually set on the VM in some cases
|
||||
may not be necessary those requested. Similar for values.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
|
||||
"qubes.PostInstall";
|
||||
"/etc/qubes/post-install.d/ scripts";
|
||||
"qvm-features-request";
|
||||
"qubes.FeaturesRequest";
|
||||
"qubes core extensions";
|
||||
"VM features";
|
||||
|
||||
"qubes.PostInstall" -> "/etc/qubes/post-install.d/ scripts";
|
||||
"/etc/qubes/post-install.d/ scripts" -> "qvm-features-request"
|
||||
[xlabel="each script calls"];
|
||||
"qvm-features-request" -> "qubes.FeaturesRequest"
|
||||
[xlabel="last script call the service to dom0"];
|
||||
"qubes.FeaturesRequest" -> "qubes core extensions"
|
||||
[xlabel="features-request event"];
|
||||
"qubes core extensions" -> "VM features"
|
||||
[xlabel="verification"];
|
||||
|
||||
}
|
||||
|
||||
Example ``/etc/qubes/post-install.d/20-example.sh`` file:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
qvm-features-request example-feature=1
|
||||
|
||||
Example extension handling the above:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import qubes.ext
|
||||
|
||||
class ExampleExtension(qubes.ext.Extension):
|
||||
# the last argument must be named untrusted_features
|
||||
@qubes.ext.handler('features-request')
|
||||
def on_features_request(self, vm, event, untrusted_features):
|
||||
# don't allow TemplateBasedVMs to request the feature - should be
|
||||
# requested by the template instead
|
||||
if hasattr(vm, 'template'):
|
||||
return
|
||||
|
||||
untrusted_value = untrusted_features.get('example-feature', None)
|
||||
# check if feature is advertised and verify its value
|
||||
if untrusted_value != '1':
|
||||
return
|
||||
value = untrusted_value
|
||||
|
||||
# and finally set the value
|
||||
vm.features['example-feature'] = value
|
||||
|
||||
Services
|
||||
---------
|
||||
|
||||
`Qubes services <https://www.qubes-os.org/doc/qubes-service/>`_ are implemented
|
||||
as features with ``service.`` prefix. The
|
||||
:py:class:`qubes.ext.services.ServicesExtension` enumerate all the features
|
||||
in form of ``service.<service-name>`` prefix and write them to QubesDB as
|
||||
``/qubes-service/<service-name>`` and value either ``0`` or ``1``.
|
||||
VM startup scripts list those entries for for each with value of ``1``, create
|
||||
``/var/run/qubes-service/<service-name>`` file. Then, it can be conveniently
|
||||
used by other scripts to check whether dom0 wishes service to be enabled or
|
||||
disabled.
|
||||
|
||||
VM package can advertise what services are supported. For that, it needs to
|
||||
request ``supported-service.<service-name>`` feature with value ``1`` according
|
||||
to description above. The :py:class:`qubes.ext.services.ServicesExtension` will
|
||||
handle such request and set this feature on VM object. ``supported-service.``
|
||||
features that stop being advertised with ``qvm-features-request`` call are
|
||||
removed. This way, it's enough to remove the file from
|
||||
``/etc/qubes/post-install.d`` (for example by uninstalling package providing
|
||||
the service) to tell dom0 the service is no longer supported. Services
|
||||
advertised by TemplateBasedVMs are currently ignored (related
|
||||
``supported-service.`` features are not set), but retrieving them may be added
|
||||
in the future. Applications checking for specific service support should use
|
||||
``vm.features.check_with_template('supported-service.<service-name>', False)``
|
||||
call on desired VM object. When enumerating all supported services, application
|
||||
should consider both the vm and its template (if any).
|
||||
|
||||
Various tools will use this information to discover if given service is
|
||||
supported. The API does not enforce service being first advertised before being
|
||||
enabled (means: there can be service which is enabled, but without matching
|
||||
``supported-service.`` feature). The list of well known services is in
|
||||
:program:`qvm-service` man page.
|
||||
|
||||
Example ``/etc/qubes/post-install.d/20-my-service.sh``:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
qvm-features-request supported-service.my-service=1
|
||||
|
||||
Services and features can be then inspected from dom0 using
|
||||
:program:`qvm-features` tool, for example:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ qvm-features my-qube
|
||||
supported-service.my-service 1
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. autoclass:: qubes.vm.Features
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. vim: ts=3 sw=3 et
|
||||
|
Loading…
Reference in New Issue
Block a user