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