core-admin/doc/qubes-features.rst
Marek Marczykowski-Górecki bc26e74339
ext: support for non-service feature advertisement
Add an API for VMs to announce support for non-service features. This is
very similar to supported-service.* features, but applies to non-service
features. This may be also used for announcing support for features that
do not use qvm-features framework itself - for example some VM kernel
features, installed drivers, packages etc.

QubesOS/qubes-issues#6030
2020-09-23 01:16:38 +02:00

219 lines
9.0 KiB
ReStructuredText

:py:mod:`qubes.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.features.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
Announcing supported features
------------------------------
For non-service features, there is similar announce mechanis to the above, but
uses ``supported-feature.`` prefix. It works like this:
1. The TemplateVM (or StandaloneVM) announces
``supported-feature.FEATURE_NAME=1`` (with ``FEATURE_NAME`` replaced with an
actual feature name) using ``qvm-features-request`` tool (see above how to use it).
2. core-admin extension
:py:class:`qubes.ext.supported_features.SupportedFeaturesExtension` records
such requests as features on the same VM.
3. Any tool that wants to check if the feature support is advertised by the VM,
can look into its features. For template-based VMs it is advised to check
also its template - there is a `vm.features.check_with_template()`
function specifically for this.
Note that (similar to services) it is not necessary for the feature to be
advertised to enable it. In fact, many features does not need any support from
the VM side, so they will work without matching ``supported-feature.`` entry.
Whether a feature requires VM-side support, is documented on case-by-case basis
in `qvm-features` tool manual page.
Module contents
---------------
.. automodule:: qubes.features
:members:
:show-inheritance:
.. vim: ts=3 sw=3 et