Mass-Configuring OVPN in NetworkManager (on Ubuntu 18.04LTS)

So, I had a pretty large problem to figure out: How to configure 170+ different OVPN (Swedish/English) configurations in NetworkManager, without having to enter my username and password over 170 friggin’, fraggin’ times.

(NOTE: If you’re highly security conscious, then this probably isn’t going to be very palatable for you. You have been warned.)

I use [REDACTED] VPN service and they offer a zip file that you can download with containing multiple OVPN configuration files; a unique file for each server that you can use. After filtering the list down to my targets, I had a resultant list to import into Network Manager.

In order to have NetworkManager leverage ovpn, though, you need to install two packages, first.

sudo apt-get install network-manager-openvpn network-manager-openvpn-gnome

Bes sure to restart NetworkManager, afterwards, so you don’t end-up in a dissonant state.

sudo service network-manager restart

So, how do we do that? Well, it’s not as complicated as one my think and you needn’t go crafting anyting in Python or the like to do so.

for i in <location of your ovpn files>; do sudo nmcli connection import type openvpn file "$i"; done

Now that we have the files imported, we need to modify them. To do this, the easiest way is to use Python to read in the files and write our changes. Modify the script below to include your username and password.

#!/usr/bin/env python
"""Changes the exported OVPN configurations in NetworkManager to contain the username and password and autoconnect.

When mass-importing OVPN configuration files, it's necessary to overcome the hurdle of the prompt for passwords. This
script looks for the exported configuration files in NetworkManager. It writes the password configuration and the
username/password to the file. You will need to restart NetworkManager for the changes to be imported; however, it's
easier/better just to bounce the machine. (sudo init 6)

TO RUN:
    sudo python3 ModifyOvpnConfigurations.py
"""

__author__ = "felsokning"
__copyright__ = "Copyright 2019"
__license__ = "MIT"

import os
import re

source_directory = "/etc/NetworkManager/system-connections"
ovpn_files = os.listdir(source_directory)
i = 0
for ovpn in ovpn_files:
    opened_file = open(source_directory + "/" + ovpn, "r+")
    file_content = opened_file.read()
    if file_content.__contains__("mssfix=1450\n"):
        output = re.sub(r"mssfix=1450", r"mssfix=1450\npassword-flags=0", file_content)
        # TODO: Change '*' to be your username
        output2 = re.sub(r"tunnel-mtu=1500", "tunnel-mtu=1500\nusername=*", output)
        output3 = re.sub(r"service-type=org.freedesktop.NetworkManager.openvpn", r"service-type=org."
                                                                                 r"freedesktop.NetworkManager."
                                                                                 r"openvpn\n\n[vpn-secrets]\n"
                                                                                 # TODO: Change '*' to your VPN password
                                                                                 r"password=*\n",
                         output2)
        output4 = re.sub(r"password-flags=1", r"", output3)

        # Indents are important, m'kay? Without them, you do things like overwrite your wireless config files.
        # Take it from me: You do NOT want to do that.
        opened_file.truncate(0)
        opened_file.seek(0)
        opened_file.write(output4)
        opened_file.close()
        print(ovpn)

Now that the files are modified, we need to restart NetworkManager to allow the configurations to be re-read on the connection’s instantiation.

sudo service network-manager restart

(Personally, bouncing the machine was more of a formidable option, here, but that’s because I messed-up the initial script – due to indenting – and wiped all of my configuration files; thus, the warning in the script.)

Now that this has been configured, I needed a way to randomly choose which VPN connection to use, so as to not always land on a static connection. To do this, I used Python again and randomised the choice of which VPN server to connect to. (You’ll note that I’m using a pretty large seed and that’s because the default random.random() method isn’t random in a secure manner [read: it’s predictable].)

#!/usr/bin/env python
"""Randomly connections to a random VPN profile (if any are found).

Uses NetworkManager (https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html#) to
enumerate the devices and connections. First, we enumerate the wireless devices to ensure that we have one. Next, we
enumerate the VPN connections and put them into a list. After that, we randomly select one of the VPN connections to
connect to. Once we've accomplished this, we disconnect the current VPN connection (if one is found) and connect to
the randomly chosen VPN connection.

REQUIREMENTS:
    python-networkmanager

TO RUN:
    python NetworkManager_VPN.py
"""

__author__ = "felsokning"
__copyright__ = "Copyright 2019"
__license__ = "MIT"

import os
import random
import time
# Externals
import NetworkManager

# Find all of the VPN connections on the machine.
vpns = list()
current_vpn = None
wireless_device = None
connections = NetworkManager.Settings.ListConnections()
for c in connections:
    if "vpn" in c.GetSettings()['connection']['type']:
        path = c.object_path
        vpns.append(path)

# If there are no VPN connections, there's no point in proceeding.
if vpns.__len__() > 0:
    # We find the Wireless Network Interface.
    # If you're running some kind of weird, three wireless network cards situation, then...
    # Change this code to work in your use-case scenario.
    devices = NetworkManager.Device.all()
    for d in devices:
        if "wifi" in d.Driver:
            wireless_device = d

    # Validate that we found a wireless network interface
    if wireless_device is not None:
        # Get the currently active VPN connection.
        # If you're running some kind of weird, three active VPNs scenario, then...
        # Change this code to work in your use-case.
        active = NetworkManager.NetworkManager.ActiveConnections
        for a in active:
            if "vpn" in a.Type:
                current_vpn = a

        # Choose a random one to connect to. We use the far more secure random method, with a larger seed,
        # to try and prevent the random generation from being a predictable pattern (well, to try to make
        # it far less predictable with our sample, at least).
        rand = random.SystemRandom(os.urandom(99999999))
        random_int = rand.randint(0, (vpns.__len__() - 1))
        random_vpn = vpns.__getitem__(random_int)
        new_connection = NetworkManager.Connection(random_vpn)

        # Validate that we have a current VPN connection to disconnect from before we do.
        if current_vpn is not None:

            # Disconnect the old & busted.
            NetworkManager.NetworkManager.DeactivateConnection(current_vpn)

            # To prevent collision in NetworkManager, we allow background clean-up before reconnecting.
            time.sleep(10)

        # Connect the new hotness.
        print("Connecting to: {}".format(random_vpn))
        NetworkManager.NetworkManager.ActivateConnection(new_connection, wireless_device, "/")

    # No wireless interfaces were found, so let's abort.
    else:
        raise Exception("No wireless interfaces were found.")

# No VPN connections were found, so let's abort.
else:
    raise Exception("The hull has been breached and the science is leaking out! "
                    "(We didn't find any valid VPN connections on this machine via NetworkManager.)")

That should just about do it. …but WAIT! That’s not all! If you act now…

I’ve also written a script to remove all of the OVPN configurations from NetworkManager, in case you made a mistake somewhere (I know that I did and this came in pretty useful.)

#!/usr/bin/env python
"""Removes all VPN profiles found in NetworkManager.

Uses NetworkManager (https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html#) to
enumerate the connections that are specifically VPN connections. Then, we delete them, with righteous retribution and
indignation.

REQUIREMENTS:
    python-networkmanager

TO RUN:
    python3 RemoveVPNs.py
"""

__author__ = "felsokning"
__copyright__ = "Copyright 2019"
__license__ = "MIT"

import NetworkManager

# Find all of the VPN connections on the machine.
vpn_list = list()
connections = NetworkManager.Settings.ListConnections()
for c in connections:
    if "vpn" in c.GetSettings()['connection']['type']:
        vpn_list.append(c)

# We make sure we'e not spinning our wheels and/or calling delete on null.
if len(vpn_list) > 0:
    for v in vpn_list:
        print("Deleting: {}".format(v.object_path))
        v.Delete()

Now, you should have of the tools you need to mass-configure OVPN on your *nix (debian-based, probably) machine. 🙂

Happy coding!