Multiple, tailored, Mullvad VPN qubes from a single disposable template using vm-config

This guide provides an easily modified example of how one can employ vm-config to tailor an arbitrary number of named disposable VPN qubes from a single disposable Mullvad VPN template.

0. Motivation

Mullvad dropping support for OpenVPN in the coming months, necessitated finding an alternative to the dedicated VPN chains I’ve been using:

appVM > vpn-firewall > **city-specific-openvpn-qube** > sys-firewall > sys-net

Since @solene’s Mullvad VPN setup in a standalone Fedora qube works just as well in a disposable Debian template, I combined her approach with vm-config for named disposable customization, which makes tailoring Mullvad VPN qubes super simple. While this guide focuses only on the tweaks required to implement this combination for “city-specific-MullvadVPN-qubes”, one could use vm-config to modify any of the JSON config data employed by the Mullvad VPN App.

  • This guide assumes the following existence of template where Mullvad VPN app and notification packages are installed (for example, by following @den1ed’s guide)

1. Create the disposable template <MULLVAD-DVM>

Follow the qubes documentation to create a single disposable template: <MULLVAD-DVM>. Once this appVM is in place, follow @solene’s guide to build the appropriate nftable rules.

Open the Mullvad App in <MULLVAD-DVM>, setup with your account details, and toggle the desired settings. Select a default city as the exit; for this guide we’ll use NYC. If you prefer to instead select a country, or a specific server, tweak the following setup accordingly.

Persist the default Mullvad App setup

As root:

mkdir -p /rw/bind-dirs/etc/mullvad-vpn
cp /etc/mullvad-vpn/* /rw/bind-dirs/etc/mullvad-vpn/

mkdir -p /rw/config/qubes-bind-dirs.d
touch /rw/config/qubes-bind-dirs.d/50_user.conf
echo "binds+=( '/etc/mullvad-vpn' )" >> /rw/config/qubes-bind-dirs.d/50_user.conf

You should now have three JSON files in /rw/bind-dirs/etc/mullvad-vpn/: account-history.json, device.json, and settings.json. The file settings.json will begin with something like the following (line numbers added for reference):

1{
2  "relay_settings": {
3    "normal": {
4      "location": {
5        "only": {
6          "location": {
7            "city": [
8              "us",
9              "nyc"
Create the rc.local-early script

Once /rw/config/rc.local-early is in place, add the following lines to the script. This will allow one to modify the city location before the Mullvad VPN App runs in each named disposable by modifying lines 8 and 9 in the settings.json file. Be sure to replace "us" and "nyc" below with the data from your settings.json file and correctly identify the line numbers.

country=$(qubesdb-read /vm-config/country)
city=$(qubesdb-read /vm-config/city)

sed -i "8s/us/$country/" /rw/bind-dirs/etc/mullvad-vpn/settings.json
sed -i "9s/nyc/$city/" /rw/bind-dirs/etc/mullvad-vpn/settings.json

2. Create named disposable qubes

In the Create New Qube GUI, select the following options for each new city-specific-MullvadVPN-qube.

Basic properties-Name: <MULLVAD-CITY>
Disposable qubes template: <MULLVAD-DVM>
Network: sys-firewall
Applications: Mullvad VPN
Advanced Options: Provides network access to other qubes

3. Assign country and city data to each named disposable qube

Use vm-config to assign each named disposable with a distinct <COUNTRY> and <CITY> combination.

In the dom0 terminal:

qvm-features <MULLVAD-CITY> vm-config.country <COUNTRY>
qvm-features <MULLVAD-CITY> vm-config.city <CITY>

Failing to assign this data, the <MULLVAD-CITY> qube will default to the choice of exit node given by the disposable template (NYC in this example).

Close the templates to complete the setup.
  • vm-config is the key to making this simple and even possible with a single disposable template. Thanks to @ddevz for pointing it out to the forum!
  • Tailoring additional Mullvad VPN qubes with vm-config manipulation of JSON data should be straightforward extensions of this example.
7 Likes

Thanks for writing this!

I just got an idea when I saw how you implemented this. You could actually parse the system hostname to retrieve the country and city.

Let’s say mullvad-vpn-dvm-country-city for instance :slight_smile:

2 Likes

This is great; thanks for the write-up!
Would there be an easy-enough way to modify this so that the named disposable rotates through different cities and countries on each new boot/reload?

While experimenting with this, I noticed the following happened in my Whonix disposable:
hostname returns “host”, while
qubesdb-read /name returns “disp####”

so it appears the latter would more reliably implement this idea. Although I’m guessing they’ll both correctly return the name of a named disposable.

1 Like

Selecting by country alone in the Mullvad App will deliver this randomization within the selected country. The above setup implements randomization within a city.

If Bash allows for random selection from a vector (?), then passing something like random as the <COUNTRY> when applying qubesdb-read /vm-config/country could be used to trigger else/if logic in the rc.local-early script to choose a random country to insert in the settings JSON file. Seems like this would work…