Netbird Setup

The setup is largely similar to setting up tailscale, with the caveat that netbird wants DNS requests to be directed to the Netbird internal IP that the qube running the client is sitting on.
This guide is largely a rewritten version of the one mentioned above.

Setting up the NetBird agent/client

The steps are largely similar to the regular Netbird setup process with a few modifications.

Create a new template and install NetBird (and some additional software) into that template.

For additional software, you will need jq.

For Debian:

  1. Create a new template for Netbird
  2. Open a new disposable qube
  3. In the fresh disposable, grab the Netbird repository signing key:
    curl -sSL https://pkgs.netbird.io/debian/public.key | sudo gpg --dearmor --output ~/netbird-archive-keyring.gpg
  4. In the disposable, copy that key to the template using qvm-copy
  5. In the template, put the key into the keyring:
    cat ~/QubesIncoming/dispxxxx/netbird-archive-keyring.gpg | sudo gpg --dearmor --output /usr/share/keyrings/netbird-archive-keyring.gpg
  6. In the template, add the netbird repository with:
    echo 'deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main' | sudo tee /etc/apt/sources.list.d/netbird.list
  7. In the template, install jq and the Netbird agent
    sudo apt install -U jq netbird

We will also need to add some networking config magic! to make DNS work, using Kalbasit’s solution to DNS forwarding.
Create the file /etc/systemd/system/qubes-netbird-dns.service

[Unit]
Description=Forward all DNS traffic to Netbird
Requires=netbird.service
After=qubes-network.service
ConditionPathExists=/var/run/qubes-service/qubes-netbird-dns

[Service]
Type=oneshot
ExecStart=/usr/bin/qubes-netbird-dns.sh start
ExecStop=/usr/bin/qubes-netbird-dns.sh stop
RemainAfterExit=yes
Restart=on-failure

[Install]
WantedBy=multi-user.target

Then, in /usr/bin/qubes-netbird-dns.sh, we add:

#!/usr/bin/env bash

  # Slight modification of wael.nasreddine.com/qubeos/configure-tailscale-dns-on-qub

  # A very hacky script, if you know better, please fix it :)

  set -euo pipefail

  netbird_dns=$(netbird status --json | jq .netbirdIp | tr -d '"' | cut -d'/' -f1)
  readonly netbird_dns

  primary_dns=$(/usr/bin/qubesdb-read /qubes-primary-dns 2>/dev/null) || primary_dns=
  secondary_dns=$(/usr/bin/qubesdb-read /qubes-secondary-dns 2>/dev/null) || secondary_dns=
  readonly primary_dns secondary_dns

  # Protect against empty DNS server list
  if [[ -z "$primary_dns" ]] && [[ -z "$secondary_dns" ]]; then
     >&2 echo "No primary or secondary DNS servers configured, cannot continue"
     exit 1
  fi

  # Flush the current dnat-dns chain
  nft flush chain ip qubes dnat-dns  # nft is used to interact with the firewall

  for dns in $primary_dns $secondary_dns; do
     if [[ "$1" == "start" ]]; then
         nft add rule ip qubes dnat-dns ip daddr $dns udp dport 53 dnat to $netbird_dns
         nft add rule ip qubes dnat-dns ip daddr $dns tcp dport 53 dnat to $netbird_dns
     else
         nft add rule ip qubes dnat-dns ip daddr $dns udp dport 53 dnat to $dns
         nft add rule ip qubes dnat-dns ip daddr $dns tcp dport 53 dnat to $dns
     fi
  done

  nft add rule ip qubes custom-input iifname == "vif*" udp dport 53 counter accept
  nft add rule ip qubes custom-input iifname == "vif*" tcp dport 53 counter accept

(The last 2 rules are required as they allow the qube to actually accept the redirected DNS requests)

Finally, enable the new unit that we just created.
sudo systemctl enable qubes-netbird-dns.service
Then, shutdown the template.

Create a new AppVM and set up bind-dirs

We will make some bind-dirs so that the Netbird session and config persists through reboots:

sudo mkdir -p /rw/bind-dirs/var/cache/netbird
sudo mkdir -p /rw/bind-dirs/var/lib/netbird
sudo mkdir -p /rw/bind-dirs/var/log/netbird
sudo mkdir /rw/config/qubes-bind-dirs.d
echo "binds+=( '/var/lib/netbird' '/var/log/netbird' '/var/cache/netbird' )" | sudo tee /rw/config/qubes-bind-dirs.d/50_netbird.conf

Then, you can log into netbird using netbird up, either through the web browser ( I have not tested this ) or through a setup key.
Shutdown the AppVM, and set it to be a disposable template (Qube Settings → Advanced → Disposable Template).

Create the DispVM

Create a Named DispVM using the previously created disposable template.
Make sure to check “Provides network” in Qube Settings → Advanced and to add “qubes-netbird-dns” to the Qube Settings → Services.

Then, you can start the disposable and run the following to check that everything was set up correctly:

netbird status
sudo systemctl status qubes-netbird-dns

You should now have a working netbird qube, to which you can attach stuff and access the Netbird network.

Blocking leaks when VPN is down

Users may also wish to block leaks when the VPN is down. This can be done by setting nftables rules to block traffic from being forwarded.
In /rw/config/qubes-firewall-user-script, add:

nft add rule ip qubes custom-forward oifname eth0 counter drop
nft add rule ip6 qubes custom-forward oifname eth0 counter drop