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