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
 | 
				
			||||||
   qubes-vm/index
 | 
					   qubes-vm/index
 | 
				
			||||||
   qubes-events
 | 
					   qubes-events
 | 
				
			||||||
 | 
					   qubes-features
 | 
				
			||||||
   qubes-storage
 | 
					   qubes-storage
 | 
				
			||||||
   qubes-exc
 | 
					   qubes-exc
 | 
				
			||||||
   qubes-ext
 | 
					   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