Переглянути джерело

doc: document features, qvm-features-request and services

Fixes QubesOS/qubes-issues#2829
Marek Marczykowski-Górecki 5 роки тому
батько
коміт
295705a708
2 змінених файлів з 194 додано та 0 видалено
  1. 1 0
      doc/index.rst
  2. 193 0
      doc/qubes-features.rst

+ 1 - 0
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

+ 193 - 0
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 <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
+