Service call to a RemoteVM¶
General case: From one Qubes OS to another Qubes OS¶
1. Initial RPC request from Local-Qube to Remote-Qube on Local-QubesOS¶
This is the starting point where
Local-Qubeon Local-QubesOS initiates an RPC request toRemote-Qube, which is a RemoteVM.Local-Qubedoes not know thatRemote-Qubeis a RemoteVM on Remote-QubesOS. Assume the request is performed for the servicemy_servicewith argumentmy_arg.
2. RPC Policy process on Local-QubesOS¶
Local-QubesOS processes the RPC request using its policy engine. It identifies that
Remote-Qubeis a RemoteVM, meaning it is not on Local-QubesOS but is accessible throughLocal-Relay, which is a LocalVM on Local-QubesOS.Local-Relayis declared as therelayvmproperty ofLocal-Qube. The communication betweenLocal-RelayandRemote-Qubeis facilitated using a RPC service specified by thetransport_rpcproperty ofLocal-Qube(later called TRANSPORT_RPC) and available onLocal-Relay.
3. Making a Qrexec call from Local-Qube to Local-Relay¶
The original RPC request is relayed through a call made from
Local-QubetoLocal-Relayafter the policy process. This request includes all the necessary information to reachRemote-QubeviaLocal-Relay. The service name for this relayed call is:
TRANSPORT_RPC+Remote-Qube+my_service+my_arg
4. Execution of TRANSPORT_RPC on Local-Relay¶
Once
Local-Relayreceives the RPC request, it invokes the TRANSPORT_RPC service, passing along the original RPC request. In this execution,Local-Relayacts as an intermediary, packaging the request and forwarding it to its destination. It is important to note that at this level, the originalqrexecrequest is forwarded by the TRANSPORT_RPC service, and Local-QubesOSdom0is not directly managing the connection.Note
The
Remote-Qubemight not appear under the same name in Local-QubesOS. When creating a RemoteVM associated withRemote-Qube, its original name is stored as a property, which is then made available in theLocal-RelayQubesDB to establish a mapping between the local qube name and the original name. During TRANSPORT_RPC execution, QubesDB can be used to retrieve the correct name.
5. Remote-Relay forwards the RPC request to Remote-Qube on Remote-QubesOS¶
TRANSPORT_RPC extracts the original service and argument, then forwards the request to
Remote-Qubeby performing a qrexec fromRemote-Relay. The original source qubeLocal-Qubeis specified as theqrexec-client-vmargument.
6. RPC policy process on Remote-QubesOS¶
On Remote-QubesOS, the RPC policy engine processes the request to ensure that it complies with allowed policies:
Remote-QubesOS verifies that
Local-Qube(viaRemote-Relay) is authorized to executemy_servicewith the argumentmy_argonRemote-Qube.If the policy allows, the request is executed, and the response is sent back along the same relay chain from
Remote-RelaytoLocal-Relay, and finally toLocal-Qube.Note
The policy engine verifies that the correct relay is being used, but it has some limitations. For example, if you have
AppVM1,AppVM2, andAppVM3behindLocal-Relay, the relay could misrepresent the origin of a request, claiming it came fromAppVM1when it actually came fromAppVM2. However,Local-Relaycannot impersonate a request fromAppVM4, asAppVM4is not configured to route throughLocal-Relay. To mitigate such risks, end-to-end verification could be added to ensure the integrity of the request throughout the entire communication chain. (Achieving full verification would require unpacking and verifying the request at the targetdom0, adding complexity and potential attack surface.)Note
Refusing unknown connections is the simplest and most straightforward approach. In the future, it might be possible to implement a mapping system for connections from specific relays. For example, if you have
AppVM1andAppVM2connected viaLocal-Relay, any unrecognized connection throughRelayVM1could be mapped to a designated RemoteVM (e.g.,Local-Relay-unknown). This would allow unexpected or misconfigured connections to be identifiable and managed rather than outright rejected.
Particular case: registering a RemoteVM not being on a QubesOS host¶
The general design also allows defining and executing a call to a RemoteVM that is not a “qube” on a Qubes OS host.
By installing qrexec-client-vm on standard Linux or Windows machines, you can execute RPC on these platforms.
This is particularly interesting if you need to interact with, for example, KVM virtual machines, RaspberryPi devices,
standard computers with extensive resources, or computers with architectures different from those currently supported by Qubes OS.
It requires adapting the qrexec codebase to manage policies directly on the machine where qrexec-client-vm runs, rather than in dom0.
In this particular case, assume that there is a LocalVM named Local-Relay and that Remote-Relay is running as a compatible
qrexec-client-vm on a non-Qubes OS host. Since a RelayVM is itself a RemoteVM, simply define Remote-Relay as a standard
RemoteVM with the name Remote-Qube, resulting in the following sequence diagram:
SSH-based communication between Local‑Relay and Remote‑Relay¶
SSH key-based authentication is configured between all involved hosts (Local‑Relay, Remote‑Relay, and any intermediate jump hosts).
(Optional) Configure any necessary proxies or jump hosts by following the OpenSSH Cookbook guidelines: Proxies and Jump Hosts <https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts>.
Local-Qube to Local-Relay: The transport_rpc property of Remote‑Qube is used to refer to RPC call to be made from Local-Qube to Local-Relay with arguments of the form:
Remote-Qube+<service>where <service> contains the original service with argument that has been processed by policies.
- Destination lookup: The RPC script extracts Remote-Qube and queries QubesDB /remotes/Remote-Qube on Local‑Relay to verify and translate it into the actual qube name in Remote-QubesOS (in this example it is assumed to be the same).
The QubesDB path is set based on Remote-Qube property remote_name.
RPC: The RPC connects to Remote‑Relay and runs
qrexec-client-vmon that host.- Remote-QubesOS policy evaluation: Remote‑QubesOS applies its policy rules for a request from Local‑Relay to Remote‑Qube with information that source qube is Local-Qube.
The information about source qube has to be resolved to a registered RemoteVM into Remote-QubesOS referencing Local-Qube.
Below is an example Bash script implementing the qubesair.SSHProxy transport RPC. It parses a destination and service, looks up the remote Qube name, and invokes qrexec-client-vm over SSH.
#!/bin/bash
set -euo pipefail
if [[ -z "${1:-}" ]]; then
echo "Usage: $0 <target+service>"
exit 1
fi
# Extract target and service
IFS='+' read -r target service <<< "$1"
# Validate extracted values
if [[ -z "$target" || -z "$service" ]]; then
echo "Error: Invalid input format. Expected 'target+service'."
exit 1
fi
# Retrieve the remote Qube identifier
remote_qube="$(qubesdb-read "/remote/$target" 2>/dev/null || true)"
if [[ -z "$remote_qube" ]]; then
echo "Error: Could not read remote qube for target '$target'."
exit 1
fi
# Forward the qrexec call via SSH
ssh "$remote_qube" qrexec-client-vm --source-qube="$QREXEC_REMOTE_DOMAIN" "$remote_qube" "$service"
Note
Using QREXEC_REMOTE_DOMAIN directly here assumes that a RemoteVM called Local-Qube exists in Remote-QubesOS.
To ensure that any SSH connection to a given host actually lands on the corresponding Remote‑Relay, add an entry like the following to your ~/.ssh/config:
Host *
HostName <Remote-Relay_ip>
Adjust the pattern, hostname, and jump host settings to match your environment. This guarantees that SSH connections intended for a RemoteVM are transparently proxied through Remote‑Relay.