qubes-doc/developer/services/admin-api.rst
Marek Marczykowski-Górecki b93b3c571e
Convert to RST
2024-05-21 20:59:46 +02:00

846 lines
23 KiB
ReStructuredText
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

=========
Admin API
=========
*You may also be interested in the article*\ `Introducing the Qubes Admin API <https://www.qubes-os.org/news/2017/06/27/qubes-admin-api/>`__\ *.*
Goals
=====
The goals of the Admin API system is to provide a way for the user to
manage the domains without direct access to dom0.
Foreseen benefits include:
- Ability to remotely manage the Qubes OS.
- Possibility to create multi-user system, where different users are
able to use different sets of domains, possibly overlapping. This
would also require to have separate GUI domain.
The API would be used by:
- Qubes OS Manager (or any tools that would replace it)
- CLI tools, when run from another VM (and possibly also from dom0)
- remote management tools
- any custom tools
Threat model
============
TBD
Components
==========
.. figure:: /attachment/doc/admin-api-architecture.png
:alt: Admin API Architecture
Admin API Architecture
A central entity in the Qubes Admin API system is a ``qubesd`` daemon,
which holds information about all domains in the system and mediates all
actions (like starting and stopping a qube) with ``libvirtd``. The
``qubesd`` daemon also manages the ``qubes.xml`` file, which stores all
persistent state information and dispatches events to extensions. Last
but not least, ``qubesd`` is responsible for querying the RPC policy for
qrexec daemon.
The ``qubesd`` daemon may be accessed from other domains through a set
of qrexec API calls called the “Admin API”. This API is the intended
management interface supported by the Qubes OS. The API is stable. When
called, the RPC handler performs basic validation and forwards the
request to the ``qubesd`` via UNIX domain socket. The socket API is
private, unstable, and not yet documented.
The calls
=========
The API should be implemented as a set of qrexec calls. This is to make
it easy to set the policy using current mechanism.
.. list-table:: i
:widths: 15 8 8 10 20 30
:align: left
:header-rows: 1
* - call
- dest
- argument
- inside
- return
- note
* - ``admin.vmclass.List``
- ``dom0``
- `-`
- `-`
- ``<class>``
-
* - ``admin.vm.List``
- ``dom0|<vm>``
- `-`
- `-`
- ``<name> class=<class> state=<state>``
-
* - ``admin.vm.Create.<class>``
- ``dom0``
- template
- ``name=<name> label=<label>``
- `-`
-
* - ``admin.vm.CreateInPool.<class>``
- ``dom0``
- template
- ``name=<name> label=<label>``, ``pool=<pool> pool:<volume>=<pool>``
- `-`
- either use ``pool=`` to put all volumes there, or ``pool:<volume>=`` for individual volumes - both forms are not allowed at the same time
* - ``admin.vm.CreateDisposable``
- template
- `-`
- `-`
- name
- Create new DisposableVM, ``template`` is any AppVM with ``dispvm_allowed`` set to True, or ``dom0`` to use default defined in ``default_dispvm`` property of calling VM; VM created with this call will be automatically removed after its shutdown; the main difference from ``admin.vm.Create.DispVM`` is automatic (random) name generation.
* - ``admin.vm.Remove``
- vm
- `-`
- `-`
- `-`
-
* - ``admin.label.List``
- ``dom0``
- `-`
- `-`
- ``<property>``
-
* - ``admin.label.Create``
- ``dom0``
- label
- ``0xRRGGBB``
- `-`
-
* - ``admin.label.Get``
- ``dom0``
- label
- `-`
- ``0xRRGGBB``
-
* - ``admin.label.Index``
- ``dom0``
- label
- `-`
- ``<label-index>``
-
* - ``admin.label.Remove``
- ``dom0``
- label
- `-`
- `-`
-
* - ``admin.property.List``
- ``dom0``
- `-`
- `-`
- ``<property>``
-
* - ``admin.property.Get``
- ``dom0``
- property
- `-`
- ``default={True|False}`` ``type={str|int|bool|vm|label|list} <value>``
- Type ``list`` is added in R4.1. Values are of type ``str`` and each entry is suffixed with newline character.
* - ``admin.property.GetAll``
- ``dom0``
- `-`
- `-`
- ``<property-name> <full-value-as-in-property.Get>``
- Get all the properties in one call. Each property is returned on a separate line and use the same value encoding as property.Get method, with an exception that newlines are encoded as literal ``\n`` and literal ``\`` are encoded as ``\\``.
* - ``admin.property.GetDefault``
- ``dom0``
- propety
- `-`
- ``type={str|int|bool|vm|label|list} <value>``
- Type ``list`` is added in R4.1. Values are of type ``str`` and each entry is suffixed with newline character.
* - ``admin.property.Help``
- ``dom0``
- property
- `-`
- ``help``
-
* - ``admin.property.HelpRst``
- ``dom0``
- property
- `-`
- ``help.rst``
-
* - ``admin.property.Reset``
- ``dom0``
- property
- `-`
- `-`
-
* - ``admin.property.Set``
- ``dom0``
- property
- value
- `-`
-
* - ``admin.vm.property.List``
- ``vm``
- `-`
- `-`
- ``<property>``
-
* - ``admin.vm.property.Get``
- ``vm``
- property
- `-`
- ``default={True|False}`` ``type={str|int|bool|vm|label|list} <value>``
-
* - ``admin.vm.property.GetAll``
- ``vm``
- `-`
- `-`
- ``<property-name> <full-value-as-in-property.Get>``
- Get all the properties in one call. Each property is returned on a separate line and use the same value encoding as property.Get method, with an exception that newlines are encoded as literal ``\n`` and literal ``\`` are encoded as ``\\``.
* - ``admin.vm.property.GetDefault``
- ``vm``
- property
- `-`
- ``type={str|int|bool|vm|label|type} <value>``
- Type ``list`` is added in R4.1. Each list entry is suffixed with a newline character
* - ``admin.vm.property.Help``
- ``vm``
- property
- `-`
- ``help``
-
* - ``admin.vm.property.HelpRst``
- ``vm``
- property
- `-`
- ``help.rst``
-
* - ``admin.vm.property.Reset``
- ``vm``
- property
- `-`
- `-`
-
* - ``admin.vm.property.Set``
- ``vm``
- property
- value
- `-`
-
* - ``admin.vm.feature.List``
- ``vm``
- `-`
- `-`
- ``<feature>``
-
* - ``admin.vm.feature.Get``
- ``vm``
- feature
- `-`
- value
-
* - ``admin.vm.feature.CheckWithTemplate``
- ``vm``
- feature
- `-`
- value
-
* - ``admin.vm.feature.CheckWithNetvm``
- ``vm``
- feature
- `-`
- value
-
* - ``admin.vm.feature.CheckWithAdminVM``
- ``vm``
- feature
- `-`
- value
-
* - ``admin.vm.feature.CheckWithTemplateAndAdminVM``
- ``vm``
- feature
- `-`
- value
-
* - ``admin.vm.feature.Remove``
- vm
- feature
- `-`
- `-`
-
* - ``admin.vm.feature.Set``
- vm
- feature
- value
- `-`
-
* - ``admin.vm.tag.List``
- vm
- `-`
- `-`
- ``<tag>``
-
* - ``admin.vm.tag.Get``
- vm
- tag
- `-`
- ``0`` or ``1``
- retcode?
* - ``admin.vm.tag.Remove``
- vm
- tag
- `-`
- `-`
-
* - ``admin.vm.tag.Set``
- vm
- tag
- `-`
- `-`
-
* - ``admin.vm.firewall.Get``
- vm
- `-`
- `-`
- ``<rule>``
- rules syntax as in :doc:`firewall interface </developer/debugging/vm-interface>` (Firewall Rules in 4x) with addition of ``expire=`` and ``comment=`` options; ``comment=`` (if present) must be the last option
* - ``admin.vm.firewall.Set``
- vm
- `-`
- ``<rule>``
- `-`
- set firewall rules, see ``admin.vm.firewall.Get`` for syntax
* - ``admin.vm.firewall.Reload``
- vm
- `-`
- `-`
- `-`
- force reload firewall without changing any rule
* - ``admin.vm.device.<class>.Attach``
- vm
- device
- options
- `-`
- ``device`` is in form ``<backend-name>+<device-ident>`` optional options given in ``key=value`` format, separated with spaces; options can include ``persistent=True`` to "persistently" attach the device (default is temporary)
* - ``admin.vm.device.<class>.Detach``
- vm
- device
- `-`
- `-`
- ``device`` is in form ``<backend-name>+<device-ident>``
* - ``admin.vm.device.<class>.Set.persistent``
- vm
- device
- ``True|False``
- `-`
- ``device`` is in form ``<backend-name>+<device-ident>``
* - ``admin.vm.device.<class>.List``
- vm
- `-`
- `-`
- ``<device> <options>``
- options can include ``persistent=True`` for "persistently" attached devices (default is temporary)
* - ``admin.vm.device.<class>.Available``
- vm
- device-ident
- `-`
- ``<device-ident> <properties> description=<desc>``
- optional service argument may be used to get info about a single device, optional (device class specific) properties are in ``key=value`` form, `description` must be the last one and is the only one allowed to contain spaces
* - ``admin.pool.List``
- ``dom0``
- `-`
- `-`
- ``<pool>``
-
* - ``admin.pool.ListDrivers``
- ``dom0``
- `-`
- `-`
- ``<pool-driver> <property> ...``
- Properties allowed in ``admin.pool.Add``
* - ``admin.pool.Info``
- ``dom0``
- pool
- `-`
- ``<property>=<value>``
-
* - ``admin.pool.Add``
- ``dom0``
- driver
- ``<property>=<value>``
- `-`
-
* - ``admin.pool.Set.revisions_to_keep``
- ``dom0``
- pool
- ``<value>``
- `-`
-
* - ``admin.pool.Remove``
- ``dom0``
- pool
- `-`
- `-`
-
* - ``admin.pool.volume.List``
- ``dom0``
- pool
- `-`
- volume id
-
* - ``admin.pool.volume.Info``
- ``dom0``
- pool
- vid
- ``<property>=<value>``
-
* - ``admin.pool.volume.Set.revisions_to_keep``
- ``dom0``
- pool
- ``<vid> <value>``
- `-`
-
* - ``admin.pool.volume.ListSnapshots``
- ``dom0``
- pool
- vid
- ``<snapshot>``
-
* - ``admin.pool.volume.Snapshot``
- ``dom0``
- pool
- vid
- snapshot
-
* - ``admin.pool.volume.Revert``
- ``dom0``
- pool
- ``<vid> <snapshot>``
- `-`
-
* - ``admin.pool.volume.Resize``
- ``dom0``
- pool
- ``<vid> <size_in_bytes>``
- `-`
-
* - ``admin.pool.volume.Import``
- ``dom0``
- pool
- ``<vid> <raw volume data>``
- `-`
-
* - ``admin.pool.volume.CloneFrom``
- ``dom0``
- pool
- vid
- token, to be used in ``admin.pool.volume.CloneTo``
- obtain a token to copy volume ``vid`` in ``pool``; the token is one time use only, it's invalidated by ``admin.pool.volume.CloneTo``, even if the operation fails
* - ``admin.pool.volume.CloneTo``
- ``dom0``
- pool
- ``<vid> <token>``
- `-`
- copy volume pointed by a token to volume ``vid`` in ``pool``
* - ``admin.vm.volume.List``
- vm
- `-`
- `-`
- ``<volume>``
- ``<volume>`` is per-VM volume name (``root``, ``private``, etc), ``<vid>`` is pool-unique volume id
* - ``admin.vm.volume.Info``
- vm
- volume
- `-`
- ``<property>=<value>``
-
* - ``admin.vm.volume.Set.revisions_to_keep``
- vm
- volume
- value
- `-`
-
* - ``admin.vm.volume.ListSnapshots``
- vm
- volume
- `-`
- snapshot
- duplicate of ``admin.pool.volume.``, but with other call params
* - ``admin.vm.volume.Snapshot``
- vm
- volume
- `-`
- snapshot
- id.
* - ``admin.vm.volume.Revert``
- vm
- volume
- snapshot
- `-`
- id.
* - ``admin.vm.volume.Resize``
- vm
- volume
- size_in_bytes
- `-`
- id.
* - ``admin.vm.volume.Import``
- vm
- volume
- raw volume data
- `-`
- id.
* - ``admin.vm.volume.ImportWithSize``
- vm
- volume
- ``<size_in_bytes> <raw volume data>``
- `-`
- new version of ``admin.vm.volume.Import``, allows new volume to be different size
* - ``admin.vm.volume.Clear``
- vm
- volume
- `-`
- `-`
- clear contents of volume
* - ``admin.vm.volume.CloneFrom``
- vm
- volume
- `-`
- token, to be used in ``admin.vm.volume.CloneTo``
- obtain a token to copy ``volume`` of ``vm``; the token is one time use only, it's invalidated by ``admin.vm.volume.CloneTo``, even if the operation fails
* - ``admin.vm.volume.CloneTo``
- vm
- volume
- token, obtained with ``admin.vm.volume.CloneFrom``
- `-`
- copy volume pointed by a token to ``volume`` of ``vm``
* - ``admin.vm.CurrentState``
- vm
- `-`
- `-`
- ``<state-property>=<value>``
- state properties: ``power_state``, ``mem``, ``mem_static_max``, ``cputime``
* - ``admin.vm.Start``
- vm
- `-`
- `-`
- `-`
-
* - ``admin.vm.Shutdown``
- vm
- `-`
- `-`
- `-`
-
* - ``admin.vm.Pause``
- vm
- `-`
- `-`
- `-`
-
* - ``admin.vm.Unpause``
- vm
- `-`
- `-`
- `-`
-
* - ``admin.vm.Kill``
- vm
- `-`
- `-`
- `-`
-
* - ``admin.backup.Execute``
- ``dom0``
- config id
- `-`
- `-`
- config in ``/etc/qubes/backup/<id>.conf``, only one backup operation of given ``config id`` can be running at once
* - ``admin.backup.Info``
- ``dom0``
- config id
- `-`
- backup info
- info what would be included in the backup
* - ``admin.backup.Cancel``
- ``dom0``
- config id
- `-`
- `-`
- cancel running backup operation
* - ``admin.Events``
- ``dom0|vm``
- `-`
- `-`
- events
-
* - ``admin.vm.Stats``
- ``dom0|vm``
- `-`
- `-`
- ``vm-stats`` events, see below
- emit VM statistics (CPU, memory usage) in form of events
Volume properties:
- ``pool``
- ``vid``
- ``size``
- ``usage``
- ``rw``
- ``source``
- ``save_on_stop``
- ``snap_on_start``
- ``revisions_to_keep``
- ``is_outdated``
Method ``admin.vm.Stats`` returns ``vm-stats`` events every
``stats_interval`` seconds, for every running VM. Parameters of
``vm-stats`` events:
- ``memory_kb`` - memory usage in kB
- ``cpu_time`` - absolute CPU time (in milliseconds) spent by the VM
since its startup, normalized for one CPU
- ``cpu_usage`` - CPU usage in percents
Returned messages
=================
First byte of a message is a message type. This is 8 bit non-zero
integer. Values start at 0x30 (48, ``'0'``, zero digit in ASCII) for
readability in hexdump. Next byte must be 0x00 (a separator).
This alternatively can be thought of as zero-terminated string
containing single ASCII digit.
OK (0)
------
::
30 00 <content>
Server will close the connection after delivering single message.
EVENT (1)
---------
::
31 00 <subject> 00 <event> 00 ( <key> 00 <value> 00 )* 00
Events are returned as stream of messages in selected API calls.
Normally server will not close the connection.
A method yielding events will not ever return a ``OK`` or ``EXCEPTION``
message.
When calling such method, it will produce an artificial event
``connection-established`` just after connection, to help avoiding race
conditions during event handler registration.
EXCEPTION (2)
-------------
::
32 00 <type> 00 ( <traceback> )? 00 <format string> 00 ( <field> 00 )*
Server will close the connection.
Traceback may be empty, can be enabled server-side as part of debug
mode. Delimiting zero-byte is always present.
Fields are should substituted into ``%``-style format string, possibly
after client-side translation, to form final message to be displayed
unto user. Server does not by itself support translation.
Tags
====
The tags provided can be used to write custom policies. They are not
used in a default Qubes OS installation. However, they are created
anyway.
- ``created-by-<vm>`` Created in an extension to qubesd at the moment
of creation of the VM. Cannot be changed via API, which is also
enforced by this extension.
- ``managed-by-<vm>`` Can be used for the same purpose, but it is not
created automatically, nor is it forbidden to set or reset this tag.
Backup profile
==============
Backup-related calls do not allow (yet) to specify what should be
included in the backup. This needs to be configured separately in dom0,
with a backup profile, stored in ``/etc/qubes/backup/<profile>.conf``.
The file use yaml syntax and have following settings:
- ``include`` - list of VMs to include, can also contains tags using
``$tag:some-tag`` syntax or all VMs of given type using
``$type:AppVM``, known from qrexec policy
- ``exclude`` - list of VMs to exclude, after evaluating ``include``
setting
- ``destination_vm`` - VM to which the backup should be send
- ``destination_path`` - path to which backup should be written in
``destination_vm``. This setting is given to ``qubes.Backup`` service
and technically its up to it how to interpret it. In current
implementation it is interpreted as a directory where a new file
should be written (with a name based on the current timestamp), or a
command where the backup should be piped to
- ``compression`` - should the backup be compressed (default: True)?
The value can be either ``False`` or ``True`` for default
compression, or a compression command (needs to accept ``-d``
argument for decompression)
- ``passphrase_text`` - passphrase used to encrypt and integrity
protect the backup
- ``passphrase_vm`` - VM which should be asked what backup passphrase
should be used. The asking is performed using
``qubes.BackupPassphrase+profile_name`` service, which is expected to
output chosen passphrase to its stdout. Empty output cancel the
backup operation. This service can be used either to ask the user
interactively, or to have some automated passphrase handling (for
example: generate randomly, then encrypt with a public key and send
somewhere)
Not all settings needs to be set.
Example backup profile:
.. code:: yaml
# Backup only selected VMs
include:
- work
- personal
- vault
- banking
# Store the backup on external disk
destination_vm: sys-usb
destination_path: /media/my-backup-disk
# Use static passphrase
passphrase_text: "My$Very!@Strong23Passphrase"
And slightly more advanced one:
.. code:: yaml
# Include all VMs with a few exceptions
include:
- $type:AppVM
- $type:TemplateVM
- $type:StandaloneVM
exclude:
- untrusted
- $tag:do-not-backup
# parallel gzip for faster backup
compression: pigz
# ask 'vault' VM for the backup passphrase
passphrase_vm: vault
# send the (encrypted) backup directly to remote server
destination_vm: sys-net
destination_path: ncftpput -u my-ftp-username -p my-ftp-pass -c my-ftp-server /directory/for/backups
General notes
=============
- there is no provision for ``qvm-run``, but there already exists
``qubes.VMShell`` call
- generally actions ``*.List`` return a list of objects and have
“object identifier” as first word in a row. Such action can be also
called with “object identifier” in argument to get only a single
entry (in the same format).
- closing qrexec connection normally does *not* interrupt running
operation; this is important to avoid leaving the system in
inconsistent state
- actual operation starts only after caller send all the parameters
(including a payload), signaled by sending EOF mark; there is no
support for interactive protocols, to keep the protocol reasonable
simple
Policy admin API
================
There is also an API to view and update :doc:`Qubes RPC policy files </developer/services/qrexec>` in dom0. All of the following calls have dom0 as
destination:
+------------------+----------+------------------+------------------+
| call | argument | inside | return |
+==================+==========+==================+==================+
| ``policy.List`` | - | - | ``<name1> |
| ``polic | | | \n<name2>\n...`` |
| y.include.List`` | | | |
+------------------+----------+------------------+------------------+
| ``policy.Get`` | name | - | ``<tok |
| ``poli | | | en>\n<content>`` |
| cy.include.Get`` | | | |
+------------------+----------+------------------+------------------+
| `` | name | ``<tok | - |
| policy.Replace`` | | en>\n<content>`` | |
| ``policy.i | | | |
| nclude.Replace`` | | | |
+------------------+----------+------------------+------------------+
| ` | name | ``<token>`` | - |
| `policy.Remove`` | | | |
| ``policy. | | | |
| include.Remove`` | | | |
+------------------+----------+------------------+------------------+
The ``policy.*`` calls refer to main policy files
(``/etc/qubes/policy.d/``), and the ``policy.include.*`` calls refer to
the include directory (``/etc/qubes/policy.d/include/``). The
``.policy`` extension for files in the main directory is always omitted.
The responses do not follow admin API protocol, but signal error using
an exit code and a message on stdout.
The changes are validated before saving, so that the policy cannot end
up in an invalid state (e.g. syntax error, missing include file).
In addition, there is a mechanism to prevent concurrent modifications of
the policy files:
- A ``*.Get`` call returns a file along with a *token* (currently
implemented as a hash of the file).
- When calling ``Replace`` or ``Remove``, you need to include the
current token as first line. If the token does not match, the
modification will fail.
- When adding a new file using ``Replace``, pass ``new`` as token. This
will ensure that the file does not exist before adding.
- To skip the check, pass ``any`` as token.
TODO
====
- notifications
- how to constrain the events?
- how to pass the parameters? maybe XML, since this is trusted
anyway and parser may be complicated
- how to constrain the possible values for ``admin.vm.property.Set``
etc, like “you can change ``netvm``, but you have to pick from this
set”; this currently can be done by writing an extension
- a call for executing ``*.desktop`` file from
``/usr/share/applications``, for use with appmenus without giving
access to ``qubes.VMShell``; currently this can be done by writing
custom qrexec calls
- maybe some generator for ``.desktop`` for appmenus, which would wrap
calls in ``qrexec-client-vm``
.. raw:: html
<!-- vim: set ts=4 sts=4 sw=4 et : -->