ProtonVPN, without using protonvpn app. (Qubes OS 4.2.0)

The ProntonVPN openvpn services can be used without using the protonvpn app, by using shell scripts and Network Manager to configure the VPN connection.

I’m going to base this guide on Debian 12, but most of the steps should be identical for Fedora, minimal templates might require extra packages to be installed.

Network Manager
The package network-manager-openvpn-gnome needs to be installed, it allows you to use openvpn with network manager.
apt install network-manager-openvpn-gnome

Sys-vpn
Create a new qube called sys-vpn with the option “provides networking” enabled, after it’s created go to the services tab and add the service network-manager.

The following steps are done in sys-vpn.

Download the openvpn config files
Log into your ProtonVPN account, and download the standard and secure openvpn files for all servers.
Unpack the standard config files into /home/user/proton/ovpn, and the secure core files into /home/user/proton/ovpn-secure.

VPN password and username
While logged into your ProtonVPN account copy your vpn username and password, it’s on the same page as the config files.

ProtonVPN DIY client
Create the file /home/user/proton/protonvpn.sh

protonvpn.sh content
#!/bin/bash

username=YOURUSERNAME

enableVPN() {
	nmcli con import type openvpn file $1
	id=${1#*/}
	id=${id%.*}
	nmcli con modify $id +vpn.data "username=$username"
	nmcli con up $id passwd-file pass.txt
}

cleanConnections() {
	for conn in $(nmcli conn show | grep vpn)
	do
		if [[ $conn == *"protonvpn"* ]]; then 
			nmcli conn delete $conn
		fi
	done
}

loadRandom() {
	echo "load random"
	file=$(find ovpn/ -type f | shuf -n 1)

	cleanConnections
	enableVPN $file
}

loadCountry() {
	echo "load country"
	IFS=':' read -r -a ccs <<< "$1"
	cc=${ccs[ $RANDOM % ${#ccs[@]} ]}
	file=$(find ovpn/node-$cc* -type f | shuf -n1)
	
	cleanConnections
	enableVPN $file
}

if [ -z "$1" ]
then
	loadRandom
	echo "load random"
else
	loadCountry $1
fi

Create the file /home/user/proton/protonvpn-secure.sh

protonvpn-secure.sh content
#!/bin/bash

username=YOURUSERNAME

enableVPN() {
	nmcli con import type openvpn file $1
	id=${1#*/}
	id=${id%.*}
	nmcli con modify $id +vpn.data "username=$username"
	nmcli con up $id passwd-file pass.txt
}

cleanConnections() {
	for conn in $(nmcli conn show | grep vpn)
	do
		if [[ $conn == *"protonvpn"* ]]; then 
			nmcli conn delete $conn
		fi
	done
}

loadSecurecore() {
	echo "load securecore"
	file=$(find ovpn-secure/ -type f | shuf -n 1)

	cleanConnections
	enableVPN $file
}

loadSecureCore

In both files add your username to the username line at the top of the file.

Create the file /home/user/proton/pass.txt and add the line:
vpn.secret.password:YOUR-VPN-PASSWORD

You should now be able to connect the VPN using the shell script, the network manager icon will show the swirl animation while connecting, and the lock when the connection is established.

./protonvpn.sh will connect you to a random server.
./protonvpn.sh cc will connect you to a random server in that country.
./protonvpn.sh cc1:cc2:cc3 will connect to a random server in any of the countries.
./protonvpn-secure.sh will connect you to a random secure core server.

Rerunning the script will disconnect you from your current server, and reconnect you to a new random server.

Firewall (optional)
If you only want hosts using sys-vpn to be able to connect to the internet though the vpn, you can add the following rules to /rw/config/qubes-firewall-user-script in sys-vpn.
nft 'insert rule ip qubes custom-forward iifname "eth0" counter drop'
nft 'insert rule ip qubes custom-forward oifname "eth0" counter drop'

Autostart (optional)
Create the file /home/user/.config/autostart/protonvpn-autostart.desktop

protonvpn-autostart.desktop content
[Desktop Entry]
Type=Application
Name=ProtonVPN Autostart
Exec="/home/user/proton/protonvpn-connect.desktop.sh"
X-GNOME-Autostart-enable=true

Start menu (optional)
In /home/user/.local/share/applications create the files:
protonvpn-connect.desktop
protonvpn-disconnect.desktop
protonvpn-securecore.desktop

.dekstop file content
[Desktop Entry]
Name=
Exec=
Terminal=false
Type=Application
Icon=protonvpn-logo
Comment=ProtonVPN script
Categories=Network;
Keywords=vpn;

In protonvpn-connect.desktop use the lines

Name=Proton VPN: Connect VPN
Exec=/home/user/proton/protonvpn-connect.desktop.sh

In protonvpn-disconnect.desktop use the lines

Name=Proton VPN: Disconnect VPN
Exec=/home/user/proton/protonvpn-disconnect.desktop.sh

In protonvpn-securecore.desktop use the lines

Name=Proton VPN: Connect Secure core VPN
Exec=/home/user/proton/protonvpn-secure.desktop.sh

Create the file /home/user/proton/protonvpn-connect.desktop.sh

protonvpn-connect.desktop.sh content
#!/bin/bash

cd /home/user/proton
./protonvpn.sh

Create the file /home/user/proton/protonvpn-disconnect.desktop.sh

protonvpn-disconnect.desktop.sh content
#!/bin/bash

for conn in $(nmcli conn show | grep vpn)
do
	if [[ $conn == *"protonvpn"* ]]; then 
		nmcli conn delete $conn
	fi
done

Create the file /home/user/proton/protonvpn-securecore.desktop.sh

protonvpn-securecore.desktop.sh content
#!/bin/bash

cd /home/user/proton
./protonvpn-secure.sh

The menu buttons will connect and disconnect the VPN, edit the protonvpn-connect.desktop.sh script if you want country code connect when you use the start menu

5 Likes

There will be DNS leak with default firewall config so you’ll need to redirect DNS queries from qubes connected to sys-vpn to Qubes OS virtual DNS addresses (10.139.1.1/10.139.1.2) to DNS server provided by your VPN provider:

An other possible way is to create a vpn qube like described in this guide (beginning from 4.2 while using the ported version of the qubes-vpn-support script) and using the downloaded .ovpn files and username/password.

In my experience, DNS leaks lead to prison sentences. Ensuring that does not happen can not be left to the user’s situational awareness, a fail closed construct is a must.

I am just starting to tinker with Qubes, that being said.

For my first VPN setup I cloned sys-net, installed OpenVPN and tmux, then ran the VPN under tmux. That’s ghetto fab for initial work and I like having tmux with a logging plugin running. Long term there should be a service to do this, but I haven’t even scratched the surface on the appropriate method to accomplish that.

Having a default route is just a convention most of us never stop to consider. When I create a VPN shim VM to use with Proton this is the relevant stuff in /etc/netplan. I used an X there because formatting, the right thing is a # to comment out the default.

    routes:

X - to: 0.0.0.0/0
X via: 192.168.88.1
- to: 185.159.156.0/23
via: 192.168.88.1

While we’re on the topic, OpenVPN is a daemon, it requires this metaphor of dialing a connection, for all the world like some screeching 20th century modem. Wireguard is a kernel level policy, it’s just there, once properly configured, and it’s always on.

I’ve had Proton running with Wireguard, but only a little bit, and it was badly behaved, but it was really new back then. If there’s an example sys-net with Wireguard as an option that would be a much better choice, so long as the use case works with having a single constant exit from the VPN.

The ‘dial’ method described here seems like it would do a better job if random exit selection were a desired feature.

Thank you for the detailed guide and scripts!

A couple of ideas:

  1. After setting up the qube described in the guide as sys-vpn, set it to provide disposable template, create a named disposable sys-vpn-2 based off sys-vpn, and use sys-vpn-2 as the NetVM. If doing so, sys-vpn should have “Provides networking” disabled, and sys-vpn-2 should have it enabled.
  2. If using Qubes 4.1, use Firewall iptables rules as described here:
iptables -I FORWARD -o eth0 -j DROP
iptables -I FORWARD -i eth0 -j DROP
ip6tables -I FORWARD -o eth0 -j DROP
ip6tables -I FORWARD -i eth0 -j DROP
1 Like

Are there any concerns with storing the VPN password in plaintext?

Create the file /home/user/proton/pass.txt and add the line:
vpn.secret.password:YOUR-VPN-PASSWORD

Besides programming nmcli with the –ask flag, what are Qubes-appropriate methods either to not store a plaintext password or to input an encrypted or hashed password into the scripts?

I don’t think so, anyone with access to the qube can get the VPN password from nm regardless of it being in the file, and all it does is give access to a $5 a month VPN service.