Wireguard VPN setup

Good news, with fedora-38 the network manager supports Wireguard out of the box!
The only thing required are extra firewall rules in the VPN qube, as explained in the community documentation about VPN.

What you’ll need

This guide assumes you are using a VPN service that has wireguard support, most of them do, but you can also add your own if you have a server.

:information_source: ProtonVPN has a free plan, it has limits but gives you a fully working VPN and they support WireGuard. This provider is recommended on privacyguides.com for being privacy friendly (no logs)

Create a new qube providing network

:information_source: Make sure fedora-40 template is installed (instructions) (so far, this works with fedora 38/39/40 and certainly later versions)

Menu » Qubes Tools » Create Qubes VM:

  • name: sys-vpn (you pick yours)
  • template: fedora-38
  • type: app qube (or AppVM)
  • networking: sys-firewall (:warning: make sure it is NOT “(default) sys-firewall” but instead “sys-firewall)
  • :ballot_box_with_check: Launch settings after creation
  • advanced (tab) » Provides network access to other qubes

And then OK. Then the qube settings window should show up (proceed to the next step).

Enable service “network-manager” in sys-vpn

In the settings window that popped up, go to the Services, select network-manager from the drop-down list and click :heavy_plus_sign: add. Then save the settings by clicking OK.

Get your wireguard VPN configuration file

Go your VPN provider and download a configuration file for wireguard (e.g.: vpn.conf)
On your VPN provider download the wireguard configuration for the server you want to connect to.

Here are the download pages for some popular VPN services: Mullvad, ProtonVPN

Use the Qube GUI to set the firewall to the VPN endpoint (this avoids leaks)

:information_source: This is essentially a killswitch. It is a fail-safe that ensures that if your VPN connection fails, it does NOT let anything through.

:information_source: This lets pass DNS requests to internal Qubes OS DNS servers and also ICMP, see the next section if you need to block everything

Open the configuration file in a text editor and take note of the IP address right next to Endpoint. If the line looks like the following, then you take note of the IP address as 1.2.3.4.

Endpoint = 1.2.3.4:5555

Open the qube settings for sys-vpn and navigate to the Firewall rules tab and set :radio_button: Limit outgoing connections to.

Then click on :heavy_plus_sign: to add a new firewall and add your saved IP address(es)

ip

Hit OK to apply and click OK again to apply the settings.

Block all traffic outside VPN using command line Qubes OS Firewall

To configure killswitch + (DNS) leak protection + ICMP/ping blocking + protection in case of sys-vpn compromise, you can alternatively execute a three-liner in dom0, no nft/iptables needed:

qvm-firewall sys-vpn reset # (1)
qvm-firewall sys-vpn add accept dsthost=1.2.3.4 # (2)
qvm-firewall sys-vpn del --rule-no 0 # (3)

(1) resets firewall to one single rule accepting everything
(2) whitelists specific VPN gateway IP
(3) removes rule (1), so there is just one whitelisted IP from (2) left

Everything else is blocked safely outside sys-vpn.

Configure your VPN in the Network Manager

On the qube on which you downloaded your wireguard configuration (e.g.: vpn.conf), open the file explorer where the file was saved. Then right-click a file and Copy to another AppVM and choose to sys-vpn as the target.

Open your file manager in sys-vpn and find the <NAME>.conf file you just copied. It should in the directory QubesIncoming. Move it to your home directory.

Open the Terminal application on your sys-vpn qube and run the following command (replacing <NAME>.conf with the correct name of the file):

nmcli connection import type wireguard file vpn.conf

If successful, you should see a notification about successful connection. If that doesn’t happen, something may be wrong with your config file:

oppp

:information_source: You should also see an icon with a padlock oppp(1) in the top-right corner of your screen (system tray). This indicates that your VPN connection is active. Without a padlock, means that it failed to connect.

Assign a VM to the new Qube network to use the VPN

Now that the VPN is configured, for each qube that you want to connect to the VPN, open its settings and set networking to sys-vpn. If you want this to be the default net qube, then you can set it in the Qubes Global Settings.

After that you’re done :partying_face:

Some websites aren’t working

This is certainly related to an MTU issue (the packet payload size), I got this issue only with www.duckduckgo.com for instance, so it could be very specific.

A solution is to force the qubes using the VPN provider qube to use a lower MTU, this can be easily achieved using a firewall rule. Add this to /rw/config/qubes-firewall-user-script:

nft add rule ip qubes custom-forward tcp flags syn / syn,rst tcp option maxseg size set rt mtu

Connect to a random VPN at qube start (optional)

:information_source: If you have multiple VPNs configured in the qube, you may want one to be picked randomly at boot to connect to.

Make sure no VPN are configured for automatic connection at startup, there is a check box in the first tab of the VPN settings for that, it defaults to automatic connection.

Add this code to /rw/config/rc.local

RANDOM_VPN=$(nmcli connection show | awk '/wireguard/ { print $1 }' | sort -R | head -n 1)
nmcli connection up "$RANDOM_VPN"

Hardening (optional)

Killswitch

:information_source: You may want to force all qubes traffic to go through the VPN and block non-VPN traffic.

Add the rules below in /rw/config/qubes-firewall-user-script in sys-vpn, make sure the qube service “qubes-firewall” is enabled in the qube settings:

# Prevent the qube to forward traffic outside of the VPN
nft add rule qubes custom-forward oifname eth0 counter drop
nft add rule ip6 qubes custom-forward oifname eth0 counter drop

These firewall rules will prevent (at the qube level) routing traffic of the NetVM child qubes through the sys-vpn NetVM. In other words, it blocks network traffic for qubes using sys-vpn as a NetVM if the VPN is not up/working.

If sys-vpn qube is compromised, it is possible to remove that rule from the qube (hence the “qube level” rule) and leak the traffic, although if this happens, it is also possible to directly listen to the network traffic going through sys-vpn.

A better killswitch solution is to use the qube firewall to block all traffic except the host:port destination required to establish the VPN. The firewall rules are applied on sys-vpn NetVM and can’t be modified from sys-vpn itself. Although this is better, it still won’t protect a compromised sys-vpn to listen to the traffic passing there (it’s is actually impossible to protect).

Prevent DNS leak

:information_source: You may also want to force using a defined DNS server (9.9.9.9 in the current example) and blocking all other DNS servers (this avoids dns leaks)

# Redirect all the DNS traffic to the preferred DNS server
DNS=9.9.9.9
nft add chain qubes nat { type nat hook prerouting priority dstnat\; }
nft add rule qubes nat iifname == "vif*" tcp dport 53 dnat "$DNS"
nft add rule qubes nat iifname == "vif*" udp dport 53 dnat "$DNS"
43 Likes

This is definitely the way to go about setting up a VPN. This guide definitely needs some polishing / more details, though. I can help with that. Do you mind if I make it into a wiki?

I did not expect it to autoconnect, but it did, which is awesome. No need to setup some script to auto connect!

2 Likes

Expanding a bit more on the auto-connect thing… The way Qubes networking scripts work, they basically overwrite the saved configurations directory, which is also where it would save the setting to autoconnect.

However, with wireguard it automagically connects, making the whole setup way easier.

1 Like

Sure, I’d be happy to help with documentation in general but it’s not clear how this work except for the repo qubes-doc.

3 Likes

maybe the nmcli connect "vpn" trick in rc.local should be enough. I don’t have this VPN anymore so I can’t try it now. I’d have to make it again.

1 Like

It should all be explained here. But this in particular belongs to the community documentation, which will be migrated to the forum soon so I guess its just a matter of explanding this post you’ve already done. Do you mind if I turn it into a wiki post so I can contribute as well?

I also moved your notes about the firewall rules setting to a “hardening” section, since it seems more advanced. And I wanted to ask you what that part aimed to achieve. If it is DNS hijack protection, doesn’t the wireguard config enforce it?

Be my guest :slight_smile:

It’s more the community documentation process which looks obscure to me at the moment :angel: but I understand it’s in a migration state.

Done :slight_smile: I hope it’s to your liking. If not, just change it.

I used bullet points as headings and changed the order of two of them. I wrote it from the perspective of someone who downloads a wireguard config from a VPN provider and not from the perspective of someone who has their own. I assumed someone who has their own VPN details can navigate the document and diverge where needed.

Let me know what you think

1 Like

nice improvement, a few nitpicks

  • you forgot to mention (did I too in the original text?) to set the VM as “providing network” in the Advanced Tab
  • why are you using nmcli instead of importing from the network manager applet?
  • The endpoint firewall rule should be at the end for hardening, it makes the guide look complicated by showing IPs addresses
  • Did you try it?
2 Likes

Thanks for the effort you put in to making this.

I’m curious, if I want to setup a sys-vpn with this configuration, but I want to change the config file to different servers over time, can I do so by simply running the command below with my alternative .conf file and change the firewall setting?

nmcli connection import type wireguard file vpn.conf

Can you clarify that VPN=9.9.9.9 in the hardening section is to be replaced with the VPN server address?

The line with 9.9.9.9 should be DNS=9.9.9.9, I was certainly tired when writing this :sweat_smile:
I’ll fix it

1 Like

Somehow I had missed your feedback.

Good point. Thanks for catching that. Just fixed it.

I didn’t find a way to add a pre-existing config via the NetworkManager GUI. As far as I found, we can only import OpenVPN config files. Is there an option for import wireguard config files directly? If so, I’d love to have it there because having a 100% GUI guide is perfect.

Sorry, I don’t fully understand what you mean, but feel free to change whatever. It’s your guide after all :slight_smile:

  • click on nm-applet
  • select “VPN Connections” → “Configure VPN…”
  • click on +
  • Select “Import a saved VPN configuration…”

It worked for me with a wg-quick configuration file

It didn’t work for me, unfortunately :pensive:

My config file looks something like this: (IPs / keys replaced)

[Interface]
PrivateKey = ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
Address = X.X.X.X/32,YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY/128
DNS = X.X.X.X

[Peer]
PublicKey = KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint = Q.Q.Q.Q:51820

In your setup that worked is it something like that?

Interesting, I can’t remember my file and I don’t have it anymore.

Maybe an extra package is required? (like wg quick maybe)

I just installed wireguard-tools, which provides wg-quick but that didn’t fix it, sadly.

We are not the first ones navigating on these waters apparently:

Supposedly the code supports it:

After some more digging, it turns out that it’s not yet available. On fedora-38 we have network-manager-applet version 1.30.0 and we need at least 1.32.0. So this will probably only be possible on Fedora 39 :confused: Not even if we switch to debian 12…

Great write up, thanks! I’m using this method in 4.2 Fedora 38 as it seems the 4.1 method published (officially by Mullvad) doesn’t work. I think SELinux is preventing execution/autostart of Wireguard in /rw/config/rc.local.

This network manager method auto starts, so all is good!

One thing I like to do from that guide, is the optional advice to disable non-tunnelled pinging - from the Mullvad guide:

Disable ping (optional)

As noted in the qube Firewall rules window, those rules do not apply to DNS requests and ICMP (pings). If you want to block pings too then you can use the qvm-firewall command.

  1. Click on the Qubes app menu and open Terminal Emulator.
  2. Run qvm-firewall MullvadVPN list. Find the rule in the bottom that says “accept icmp” and note the line number.
  3. Run qvm-firewall MullvadVPN del --rule-no NUMBER. Replace NUMBER with the line number you found above.
  4. Run qvm-firewall MullvadVPN add --before NUMBER drop proto=icmp. Replace NUMBER with the line number you found above. This new rule will be added before the last “drop” line.
  5. Check it by running the list command again. The rules should be in this order: accept (the IP addresses of the VPN servers), accept dns, drop icmp, drop.
1 Like