forked from QubesOS/qubes-core-admin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
doc: document features, qvm-features-request and services
- Loading branch information
Showing
2 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
: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 or template startup. | ||
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. | ||
|
||
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 | ||