An Opensnitch Nodes implementation to filter outbound traffic from multiple qubes

This guide adapts Opensnitch Nodes for Qubes OS from scratch. One offline “Server” qube running the Opensnitch GUI can filter the traffic of multiple “Node” qubes, the latter requiring only the Opensnitch daemon service. Communication is achieved by way of the qubes.ConnectTCP service. The particular setup described below maintains compartmentalization at the expense of GUI effectiveness, so may not be the ideal setup for all use cases. It is nevertheless effective as an interactive filter.

About Opensnitch

an application firewall that allows for interactive outbound connections filtering. While the Opensnitch GUI is available for individual qubes, Opensnitch offers a server(GUI)/node(daemon) setup that is potentially better suited for filtering traffic originating from multiple qubes simultaneously.

Network-isolated Opensnitch GUI server

Opensnitch GUI Server template

1. Update & clone debian-12-minimal

If you only want a single GUI Server qube, it’s worth considering the latest release of Opensnitch in a Fedora minimal standalone. We’ll employ the Debian minimal template here.

in dom0 terminal:
$ sudo qubesctl --skip-dom0 --targets=debian-12-minimal --show-output state.sls update.qubes-vm
$ qvm-clone debian-12-minimal <DEBIAN-12-SERVER-TEMPLATE>
Note on `<ALLCAPS>`

indicates one is free to choose, in this case the name of the template.

2. Install Opensnitch

Newer releases are available on Github, including rpm packages, but we’ll install the older version available in the Debian stable repos. Once installed, Opensnitch will run automatically on startup, so it’s necessary to disable Opensnitch in the template and later enable it to run automatically in the Server and Node qubes.

in dom0 terminal:
$ qvm-run -u root --pass-io --no-gui <DEBIAN-12-SERVER-TEMPLATE> 'apt install opensnitch -y'
$ qvm-run -u root --pass-io --no-gui <DEBIAN-12-SERVER-TEMPLATE> 'systemctl stop opensnitch && systemctl disable opensnitch'

3. Verify Opensnitch is disabled in the template (optional)

Restart the template. You should see a grey cloud icon appear in the system tray. A black cloud would indicate that Opensnitch is enabled, which is undesireable in the template.

4. Modify the Opensnitch config files

In the template, modify Opensnitch daemon config files to listen on port 50051, rather than the default, which is to filter traffic originating from the Server qube itself. The configuration also defaults to iptables, which should be changed to nftables as of Qubes OS R4.2. These changes will be inherited by the GUI Server qube.

in <debian-12-server-template> root terminal:
# sed -i 's/iptables/nftables/g' /etc/opensnitchd/default-config.json
# sed -i 's/unix:\/\/\/tmp\/osui.sock/[::]:50051/g' /etc/opensnitchd/default-config.json

Close the template.

Opensnitch GUI Server qube setup

1. Create GUI Server app qube

The GUI Server qube does not require network access, so assign it a null netvm.

in dom0 terminal:
$ qvm-create --template <DEBIAN-12-SERVER-TEMPLATE> --label <COLOR> <GUI-SERVER-QUBE> 
$ qvm-prefs <GUI-SERVER-QUBE> netvm ""

2. Configure GUI Server qube

Since Opensnitch is disabled in the template, we have to reenable it in the app qube.

We also want the GUI to listen for the Node qubes. In the GUI config file, insert a line to specify a value for the variable server_address under [global]. In this case (below), we find that the end of the [global] table is at line 10, so we insert it there (ymmv). This modification should tell the GUI to listen for Node qubes on port 50051.*

in <gui-server-qube> root terminal:
# echo "systemctl enable --now opensnitch" >> /rw/config/rc.local
# less -N /home/user/.config/opensnitch/settings.conf
# sed -i '10i server_address=[::]:50051' /home/user/.config/opensnitch/settings.conf
* should, but does not...

Despite our config change, the server address will still default to unix:///tmp/osui.sock, as hard coded in the opensnitch-ui. A greyed out Opensnitch icon in the sys tray will result. It’s possible that we have not specified the server_address variable correctly, but I was unable to find example (s) in Github.
I’ll happily update this guide if anyone can identify a working specification for server_address, but until I have a fix, the next step will at least get it working.

3. Hack to modify Opensnitch default server address

Modify the executable in the template (modification in the GUI Server qube will not persist).

in <debian-12-server-template> root terminal:
# sed -i 's/unix:\/\/\/tmp\/osui.sock/[::]:50051/g' /usr/bin/opensnitch-ui
*

The Opensnitch GUI Server should now behave as expected on reboot, so we just need to set up our upstream app qubes to act as Opensnitch nodes.

Disposable Opensnitch Node qubes

Node template for app qubes and disposable templates

1. Update & clone debian-12-xfce

in dom0 terminal:
$ sudo qubesctl --skip-dom0 --targets=debian-12-xfce --show-output state.sls update.qubes-vm
$ qvm-clone debian-12-xfce <DEBIAN-12-NODE-TEMPLATE>

2. Install Opensnitch daemon

We can use the no-install-recommends flag to install the Opensnitch daemon without the GUI and its dependencies.

in dom0 terminal:
$ qvm-run -u root --pass-io --no-gui <DEBIAN-12-NODE-TEMPLATE> 'apt install --no-install-recommends opensnitch -y'
$ qvm-run -u root --pass-io --no-gui <DEBIAN-12-NODE-TEMPLATE> 'systemctl stop opensnitch && systemctl disable opensnitch'

3. Modify Opensnitch config files

In the template, modify the Opensnitch daemon config files to use nftables and to use the centralized GUI Server as the interactive filter. These changes will be inherited by the disposable template (or app qubes).

in <debian-12-node-template> root terminal:
# sed -i 's/iptables/nftables/g' /etc/opensnitchd/default-config.json
# sed -i 's/unix:\/\/\/tmp\/osui.sock/localhost:50051/g' /etc/opensnitchd/default-config.json

Close the template.

Disposable template for Node qubes

1. Create disposable template

in dom0 terminal:
$ qvm-create --template <DEBIAN-12-NODE-TEMPLATE> --label <COLOR> <DVM-NODE-TEMPLATE>
$ qvm-prefs <DVM-NODE-TEMPLATE> template_for_dispvms True
$ qvm-prefs <DVM-NODE-TEMPLATE> netvm <NETVM>
$ qvm-features <DVM-NODE-TEMPLATE> appmenus-dispvm 1

Refreshing applications under the Applications tab of the Settings Manager will allow you to show selected applications in the <dvm-node-template> app menu.

2. Expose TCP port and enable Opensnitch

Allow the (disposable) Node qubes to communicate on port 50051 at startup. Note, the RPC service qubes.ConnectTCP does not require networking between qubes.(2)

in <dvm-node-template> root terminal:
# echo "qvm-connect-tcp ::50051" >> /rw/config/rc.local
# echo "systemctl enable --now opensnitch" >> /rw/config/rc.local

Close <dvm-node-template>.

3. Set the RPC policy

Allow communication between the GUI Server and (disposable) Node qubes by tagging <dvm-node-template> and establishing the minimal necessary qubes RPC policy.

in dom0 terminal:
$ qvm-tags <DVM-NODE-TEMPLATE> add snitch
$ echo "qubes.ConnectTCP +50051 @tag:snitch @default allow target=<GUI-SERVER-QUBE>" >> /etc/qubes/policy.d/30-user.policy

With these settings, the Opensnitch GUI Server should run automatically in <gui-server-qube> and filter traffic whenever a disposable Node qube is spawned from <dvm-node-template>.

Opensnitch Rules

Rules generated in the GUI Server are saved and enforced locally in /etc/opensnitchd/rules/ of the disposable client Node. Hence, these rules will be lost when the disposable is closed. To preserve these rules, and lessen the human interaction with Opensnitch, they should be copied to <dvm-node-template>. To make them available in the Node qubes, create a new directory in the disposable template where the JSON files can persist and have them automatically copied into the default rules path.(1)

in <dvm-node-template>
# mkdir -p /rw/config/opensnitchd/rules
# echo "sudo cp /rw/config/opensnitchd/rules/* /etc/opensnitchd/rules/" >> /rw/config/rc.local 

(Notes)

Some notes and observed behaviors:

(1) Syncthing, or similar, might be helpful here.
Opensnitch releases after v1.6.5 allow modification of the rules path via the config file, so one could simply use /rw/config/opensnitchd/rules/. Since Debian Bookworm is on an older release, copying the rules into the default location will at least allow one to edit the rules in the GUI.

(2) Use of the qubes.ConnectTCP service in multiple Node qubes appears to confuse the GUI a bit. The GUI will only list the last node opened and if that disposable is closed, it will treat all nodes as offline, which causes the GUI to not display any new events. Nevertheless, the Opensnitch daemon(s) will continue to filter as expected.

(3) The node for each rule will be listed as the localhost. As a result, the rules created by any open Node qube will be applied to every other Node qube.

(4) The rules are editable in the GUI, but within the GUI there will be some ambiguity about the identity of the Node qube that originated the rule and hence where any local changes will be applied. However, once saved, the changes will be reflected immediately in the JSON files.

(5) Not sure if (4) matters in practice given (3).

While this setup is a good proof of concept, there appears to be some room for improvement, or at least alternative tradeoffs between GUI function and compartmentalization between the GUI Server qube and the Node qubes. GUI identification of nodes according to each qube’s actual IP address, rather than lumping them all together as 127.0.0.1 would be a significant improvement, but presumably at some cost… Any helpful feedback is appreciated.

2 Likes