From 295705a708b0f46fa514ccc4053dba927dd368cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Thu, 18 Oct 2018 06:30:38 +0200 Subject: [PATCH] doc: document features, qvm-features-request and services Fixes QubesOS/qubes-issues#2829 --- doc/index.rst | 1 + doc/qubes-features.rst | 193 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 doc/qubes-features.rst diff --git a/doc/index.rst b/doc/index.rst index 5d0e71d4..b375f67b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -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 diff --git a/doc/qubes-features.rst b/doc/qubes-features.rst new file mode 100644 index 00000000..a334cff9 --- /dev/null +++ b/doc/qubes-features.rst @@ -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 `_ are implemented +as features with ``service.`` prefix. The +:py:class:`qubes.ext.services.ServicesExtension` enumerate all the features +in form of ``service.`` prefix and write them to QubesDB as +``/qubes-service/`` and value either ``0`` or ``1``. +VM startup scripts list those entries for for each with value of ``1``, create +``/var/run/qubes-service/`` 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.`` 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.', 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 +