123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- :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
|