Skip to content

Installing a homemade VPN

Published:

What is a VPN?

A VPN (Virtual Private Network) is a protocol that allows you to create a virtual network, simulating a direct connection between two or more devices. VPNs are also used to ensure the security and privacy of communications over the Internet, encrypting data and hiding the user’s IP address, making it appear as if they are connected from another location.

Loading diagram...

WireGuard

WireGuard is an open-source software for creating high-performance VPNs. It is designed to be simple, fast, and secure, using modern cryptographic algorithms. It is available on various platforms, including Linux, Windows, macOS, iOS, and Android, and is designed to be easy to configure and use. It has been integrated into the Linux kernel starting from version 5.6, which means it can be used directly as a kernel module, improving performance and security.

IAC-VPN

Installing and configuring a VPN is not a complex task for anyone with a little bit of experience, but repeating it multiple times or doing it on-the-fly can be cumbersome and error-prone. To avoid this, I have prepared a series of orchestration scripts that use Terraform and Ansible to automate the entire process. The repository containing everything you need is available on GitHub.

Provisioning

To set up our topology, we need a VPS server. The easiest way to create it and ensure it is always reachable from any source is to use a cloud computing provider, although nothing prevents you from using a physical server or a local virtual machine.

The Terraform configurations I’ve prepared include Google Cloud, Oracle Cloud and OpenStack. Nothing prevents you from using a different provider, but you will need to modify the configuration files to adapt them to your provider, or even create all the necessary resources using the graphical interface provided by the provider.

To use Terraform, you need to install it and configure the credentials for the chosen provider. The procedure is different for each provider, so you should consult the official documentation. After making your choice, and after installing Terraform, you will need to:

  • Install the necessary dependencies with terraform init
  • Create a terraform.tfvars file with the necessary variables for the chosen provider
  • Run terraform plan to see which resources will be created
  • Run terraform apply to create the resources
  • Run terraform destroy to destroy the created resources
  • Run terraform output to see information about the created resources

Provisioning Script

If you are using bash, you can use the provisioning script found in the repository with

./run.sh <provider>

Manual Provisioning

If you prefer to do it manually, you will need to:

  • Create a VPS with the chosen provider
  • Ensure that the security rules allow incoming and outgoing traffic at least on TCP ports 22 (SSH), 80 (HTTP), and UDP 51820 (WireGuard)
  • Create a virtual machine with Linux (preferably Debian based)
  • Create an SSH key to access the server

WireGuard Configuration

We can now proceed with the WireGuard configuration. We use Ansible, which allows us to define a series of tasks to be executed in sequence automatically and idempotently.

To use Ansible, you will need to install it and configure the credentials for the chosen server. You will also need to configure the inventory file with the server information, such as the IP address and SSH key. Among the variables that must be defined are:

all:
  hosts:
    vpn_server:
      ansible_host: server_ip # public IP of the server
      ansible_user: ubuntu # username to access the server
    vpn_client:
      ansible_host: localhost # ip of the client
      ansible_connection: local # whether the client is local
  vars:
    wireguard:
      internet: true # if true, the vpn will be used to connect to the internet
      net: 10.0.0.0/24 # vpn network
      server:
        sk: key! | # private key of the server
        pk: pkey! | # public key of the server
        port: 51820 # port of the server
        vpn_addr: "10.0.0.1" # address that the server uses within the vpn
        out_interface: ens3 # network interface of the server to connect to the internet
      clients:
        - sk: key! | # private key of the client
          pk: pkey! | # public key of the client
          vpn_addr: "10.0.0.2" # address that the client uses within the vpn

Generating Keys

To generate the keys, you can use the command

wg genkey | tee privatekey | wg pubkey > publickey

The privatekey and publickey files will contain the private and public keys, respectively. You can then copy the contents of these files into the sk and pk fields of the inventory file. Repeat the operation for the server and for each client you want to add to the VPN.

Running Ansible

To run Ansible, you will need to execute the command

ansible-playbook -i inventory playbook.yml

or, more simply, run the script

./run.sh wireguard

Configuring the Client

Configuring the client is quite simple. You can let Ansible take care of everything, or you can do it manually. In the first case, you will need to ensure that the client’s IP is localhost and that you have added the --ask-become-pass flag to allow Ansible to run commands with root privileges.

If you have decided to do it manually, you will need to install WireGuard and configure it. In practice, you just need to add the configuration file that Ansible created for you, which is located at /etc/wireguard/wg0.conf. This will contain something like this:

# /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.2/24
PrivateKey = <chiave privata del client>
MTU = 1420

PostUp = ping -c1 10.0.0.1

[Peer]
PublicKey = <chiave pubblica del server>
Endpoint = <ip del server>:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25

Finally, to start the VPN, you will need to run the command

sudo wg-quick up wg0

Using TCP

Loading diagram...

WireGuard only supports the UDP transport protocol, which is faster and lighter than TCP. However, certain network configurations may block UDP traffic, making it impossible to use WireGuard. To work around this issue, we can use a TCP tunnel that encapsulates UDP traffic in TCP packets. I chose wstunnel, a small open-source tool that uses WebSocket for this purpose. The repository provides precompiled binaries, but nothing prevents you from building the executable from the source code, especially if your operating system’s antivirus does not like the files. After ensuring the executable is working on the system’s $PATH, you can proceed by first launching the server

wstunnel server --restrict-to localhost:51820 wss://0.0.0.0:80

and then the client

wstunnel client -L 'udp://51820:localhost:51820?timeout_sec=0' wss://<server_ip>:80
wstunnel client -L 'udp://51820:localhost:51820?timeout_sec=0' wss://<server_ip>:80