wb5/posts/wireguard.md

429 lines
18 KiB
Markdown
Raw Permalink Normal View History

2024-06-18 20:52:19 -04:00
# wireguard vpn server setup guide for linux
2024-06-17
Recently, I decided to set up a WireGuard VPN server for personal use.
I found that existing guides about this subject are all lacking in some way.
The server setup tutorials I found all go through the commands and configurations needed very throughly,
but don't explain _why_ things work.
Meanwhile, WireGuard's [_Conceptual Overview_](https://www.wireguard.com/#conceptual-overview)
focuses on the protocol itself and how it is different from others.
So, this post is a summary of my mental model of how WireGuard works,
plus a tutorial for setting up the server.
I assume that you have some knowledge of networking,
but aren't familiar with WireGuard.
I also assume Linux knowledge.
This guide should work generally, but I tested it on Arch Linux.
> **CIDR notation**:
>
> In this post, if you see an `/24` at the end of an IP address, that's [CIDR notation](https://en.m.wikipedia.org/wiki/Classless_Inter-Domain_Routing).
> `192.168.0.0/24` could be written as "any address that fits the pattern
> `192.168.0.*`". Because there are 32 bits in an IPv4 address, the `/24` at
> the end means that the first 24 bits (the first three bytes `192.168.0`) are
> fixed, and the remaining 8 bits can be anything. Also, a `/32` pattern
> matches only one address.
## what is a vpn?
A VPN (virtual private network) is often advertised to the average user as an
encrypted tunnel for their Internet connection.
Mostly, this is useful for changing your IP address.
However, this is not the full picture of what a VPN is.
In corporate networks, there are many important services that can't be exposed publicly,
which is why they're only available on an internal, private network.
Some off-site users, especially remote workers, need to access the internal network,
but from the outside Internet.
This is where a VPN can be useful: users can _virtually_ be on the private network,
as if they were on site.
Using a VPN is more secure than publicly exposing services,
as authentication allows fine-grained control over who can access the network.
For someone like me, VPNs are useful because I self-host private services.
Normally, I would need to expose them to the public Internet to access them.
Instead, I can access them solely through the local network or the VPN,
preventing strangers from even seeing the login pages of my services.
This reduces the attack surface and makes things more secure.
## wireguard setup
WireGuard is a relatively recent protocol for implementing VPNs.
It's shiny and new, and also has a slimmer codebase than older, more established protocols.
Let's go through the process of setting up a VPN to access an internal network.
The setup for tunneling all of a device's Internet traffic is similar, and I will explain it too later.
We will have a _Client_ device, and a _Server_ device.
The Server is in the internal network (let's say in the `192.168.0.0/24` subnet),
and the Client is outside of it.
There is also a publicly accessible domain `vpn.example.com` that resolves to the Server.
To recap, here is a diagram of what we're trying to do:
```
╭────────────╮
│ VPN Client │
╰─────┬──────╯
│ ╭────────────────────────────────────────────────╮
│ │ the private network (192.168.0.0/24) │
│ │ ╭───────────────────╮ │
╭───────┴─────────╮ │ │ VPN Server │ │
│ public internet ├────────┼──────┤ (vpn.example.com) ├────── ⋯ other hosts │
╰─────────────────╯ │ ╰─┬───────────────┬─╯ │
│ ╭──────┴──────╮ ╭────┴────────╮ │
│ │ machine 1 │ │ machine 2 │ │
│ │ 192.168.0.4 │ │ 192.168.0.7 │ │
│ ╰─────────────╯ ╰─────────────╯ │
│ │
╰────────────────────────────────────────────────╯
```
On Linux, one of the quicker ways to set up WireGuard is through `wg-quick`,
which is an utility that takes a config file and sets up WireGuard with it.
In Arch Linux, this comes in the `wireguard-tools` package:
```
# pacman -S wireguard-tools
```
`wg-quick` stores config files under `/etc/wireguard/`.
Each config file has the name `[name].conf`,
where the file name is used as the _interface_ name.
2024-06-18 21:25:32 -04:00
These are the same network interfaces as `wlan0` or `eth0`;
2024-06-18 20:52:19 -04:00
WireGuard typically uses names like `wg0`.
### server configuration
An important thing to understand about WireGuard is that it makes no distinction of server or client:
every device is a _peer_.
To authenticate with each other, each peer has a private key,
and a list of public keys of the peers it talks to.
This is similar to how SSH's `authorized_keys` works.
To generate both the private key and the public key at once, use this command:
```
$ wg genkey | tee /dev/tty | wg pubkey
WDax5fhKcKwdeuiYjgi4w/34ig2aZuDjmLHYnWUtfGc=
pJApgMHuIvMMsApTNXA3MMq+82nQ30XuVbAk9jsBNRs=
```
The private key will be on the first line, and the public key on the second.
Keep these keys around, as they will be useful later.
Let's now see the config file for the Server:
```
# SERVER config
# /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/32
# REPLACE THIS!
PrivateKey = [private key]
ListenPort = 51820
# make sure to replace eth0 by the appropriate interface
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = xZGlY8HIJt+rhGfbDK/T2xq0LQoR3kL6tGGVijaRBDI=
AllowedIPs = 10.0.0.2/32
```
First, let's look at the `[Interface]` section, which contains information about the Server.
It has the private key, and also the UDP port that WireGuard listens on
(remember to open this in the firewall).
The address marked here is the Server's address.
Its subnet is completely different from the LAN the Server is actually on (`192.168.0.0/24`).
This is because we're creating a brand new private network (`10.0.0.0/24`) inside the VPN connection,
where our Client and Server will coexist.
> Note:
> `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/24` are all entirely reserved for private subnets, and you will not see them on the open Internet.
> To avoid collisions, the new VPN subnet should be different from the real LAN subnets for all peers.
> OpenVPN [recommends](https://openvpn.net/community-resources/numbering-private-subnets/) obscure subnets like `10.66.77.0/24`,
> which are equivalent to the middle of nowhere in IP address space.
There is also the `PostUp` and `PostDown` fields.
These are commands run in `bash` after starting and stopping the VPN Server.
I'm not going to go into the details, but I'll explain in general what they mean:
- `iptables -A` means to add a firewall rule when starting the VPN, and `-D` is to delete the rule when the VPN stops.
- The `%i` variable is part of `wg-quick`, and it expands to the VPN interface name (e.g. `wg0`).
- These rules in general allow the Server to _forward_ packets, while [_masquerading_](https://askubuntu.com/a/1295626).
Essentially, just as a device can access the Internet through a router, the Client accesses the internal network through the Server.
To do this, the Server will act like a router and perform [NAT](https://en.wikipedia.org/wiki/Network_address_translation).
- `-o eth0` means the internal network is accessed over the `eth0` interface.
Meanwhile, in the `[Peer]` section, we write the Client's public key,
which allows it to talk to the Server.
Our Client has an address of `10.0.0.2`, but
instead of an `Address` field, we use `AllowedIPs`.
These are the IP addresses that _can be routed to and from this peer_.
Here are some examples to clarify what that means:
- Let's say the Server wants to send a packet to `10.0.0.2`.
WireGuard sees that peer `xZGlY...` (the Client) has this IP in its allowlist,
so the Server sends the packet to the Client.
- The Server wants to send a packet to `archlinux.org (95.217.163.246)`.
This IP is not in the peer's allowlist, so it will not route the packet to the Client.
- The Client sends a packet to the Server, with the source being its IP address `10.0.0.2`.
The Server sees that this IP is in the allowlist, so it accepts the packet from the Client.
- The Client, who is now evil, decides to send a packet to the Server but impersonating another IP address: `10.0.0.69`.
This IP not being in the Server's allowlist, it rejects the packet.
In general, you can see that the `AllowedIPs` field is what determines where packets can and can't go.
By setting `AllowedIPs = 10.0.0.2`, the server knows that the Client only has control over packets directed to or from that address.
It is not allowed packets to or from any other IP.
This concept of pairing the allowlist with public keys to manage packet routing is called [_cryptokey routing_](https://www.wireguard.com/#cryptokey-routing) in WireGuard.
Also,
by default, Linux disables IP forwarding. To enable it, edit `/etc/sysctl.conf` and add/uncomment the line
```
net.ipv4.ip_forward = 1
```
and run
```
# sysctl -p
```
to load the new configuration.
If your VPN server is on the public internet,
be sure to have sane firewall rules before doing this.
> Note: If you use [UFW](https://wiki.archlinux.org/title/Uncomplicated_Firewall) as a firewall like me, note that it has its own `sysctl.conf`, which lives at `/etc/ufw/sysctl.conf`.
2024-06-19 14:07:02 -04:00
> This will override the regular `sysctl` if you follow the instructions above.
> To prevent it from erasing your changes, uncomment the relevant line:
>
> ```
> # /etc/ufw/sysctl.conf
>
> # Uncomment this to allow this host to route packets between interfaces
> net/ipv4/ip_forward=1
> ```
2024-06-18 20:52:19 -04:00
2024-06-18 21:25:32 -04:00
### client configuration
2024-06-18 20:52:19 -04:00
Let's now examine the Client's configuration file.
If the client is running Linux, it will also be in `/etc/wireguard/*.conf`,
However, the `wg-quick` configuration format can be read by many other clients,
like the WireGuard Android app.
Anyways, here is the configuration file:
```
# CLIENT config
# wg0.conf
[Interface]
Address = 10.0.0.2/32
# REPLACE THIS!
PrivateKey = [private key]
# be careful not to use CIDR notation here
DNS = 10.0.0.1
[Peer]
PublicKey = M/HD4qJYi1RlMH/K9xQ12yW6Cu62LuGasyZhfnVsbUE=
# do not forget the port here
Endpoint = vpn.example.com:51820
AllowedIPs = 192.168.0.0/24
```
You'll see this configuration is very similar to the Server's,
which is natural as WireGuard devices are all peers.
There are a few differences, though.
First, there is a `DNS` field, which can be used to prevent [DNS leaks](https://en.m.wikipedia.org/wiki/DNS_leak).
This only works if the VPN server provides a DNS server too.
Otherwise, set it to some other DNS server, or remove the line.
There is also the `Endpoint` field.
This marks the Server's public address on the open Internet.
The `Endpoint` field is omitted in the Server configuration,
as it is implicit:
the Server will find out the endpoint IP when the Client reaches out to it for the first time.
> Note: technically, the `Endpoint` field not always strictly necessary.
> If `SaveConfig` is enabled in the `[Interface]` section, and either the Server or Client changes IP address
> while connected, [roaming](https://www.wireguard.com/#built-in-roaming) allows WireGuard to keep working
> and update `Endpoint` to the new address in the config file.
> I haven't tested this, though.
Most importantly, we also have the `AllowedIPs` for the server.
Here, it's an entire subnet, and not just a single address.
What this means is that if the Client wants to send a packet to a device on the internal network,
say `192.168.0.4`, WireGuard matches the IP to the subnet `192.168.0.0/24`, takes the packet and routes it through to the server,
which then forwards these packets to the machine at `192.168.0.4` in the internal network.
If the machine then replies with a packet, WireGuard sees it is from the `192.168.0.0/24` subnet, then routes it back to the Client.
Using the VPN, the Client will be able to communicate with machines in the internal network as if it were there.
## running the vpn
### wg-quick
To start/stop the VPN for both Client and Server (if they run Linux),
you can use
```
# wg-quick up wg0
# wg-quick down wg0
```
where `wg0` should be the name of your configuration file.
Alternatively, there is a SystemD service you can use:
```
# systemctl start wg-quick@wg0
# systemctl stop wg-quick@wg0
```
or, to have WireGuard start at boot:
```
# systemctl enable wg-quick@wg0
# systemctl disable wg-quick@wg0
```
To diagnose issues with WireGuard, running `wg show` can be useful:
```
# wg show
interface: wg0
public key: [snip]
private key: (hidden)
listening port: 49595
peer: [snip]
endpoint: [snip]:51820
allowed ips: 0.0.0.0/0
latest handshake: 57 seconds ago
transfer: 4.83 GiB received, 261.16 MiB sent
persistent keepalive: every 25 seconds
```
This can show that there is no handshake at all between peers,
and thus that there is no connection.
### networkmanager
There is also the option of using [NetworkManager](https://wiki.archlinux.org/title/NetworkManager),
which is neat if you already use it to manage your existing network connections.
To do this, save your configuration file, and then import it through `nmcli`:
```
$ nmcli con import type wireguard file wg0.conf
```
Then, you can manage the connection as usual through `nmcli`:
```
$ nmcli con down wg0
$ nmcli con up wg0
$ nmcli con edit wg0
```
### mobile devices
On mobile devices, WireGuard apps can also use these configuration files:
- F-Droid: [WG Tunnel](https://f-droid.org/packages/com.zaneschepke.wireguardautotunnel/)
- Google Play: [WireGuard](https://play.google.com/store/apps/details?id=com.wireguard.android)
- App Store: [WireGuard](https://apps.apple.com/us/app/wireguard/id1441195209)
You can securely transfer the config files in a normal way,
but I recommend using QR codes because it's way simpler.
Install the `qrencode` package,
and do this:
```
$ cat wg0.conf | qrencode -t ansiutf8
█████████████████████████████████████
█████████████████████████████████████
████ ▄▄▄▄▄ █▄▄▄ ▀▀▀▄▀▄█▀██ ▄▄▄▄▄ ████
████ █ █ ██▄▀ █ ██▀▀▄▄▀█ █ █ ████
████ █▄▄▄█ ██▀▄ ▄▀▄█▄▄ ▄▀█ █▄▄▄█ ████
████▄▄▄▄▄▄▄█ ▀▄█ ▀▄▀ █▄█▄█▄▄▄▄▄▄▄████
████ ▀ ▀▄▀█▄▀█▄▄▄▄█ ▀ ▄▀▄▀▀█▀▀▄████
████▀█▄ ▀█▄ ▄██▄█ ▄ ▀▀▀▀▄ ▀▄█▀████
████▄█▄▄█▄▄█▀▄ █▀▄ ▀█▀▄█ █ ▀▀▀▀▀ ████
████▀ ▀█ █▄▄▄█▀█▀▀ █▄█▄▄▄▄ █ █ ████
████▀▄▀▀▄▀▄██ ▄██ ▄▀█ ███▀▄▀▀█████
████ ██▀▄▄█▀▀█▀▄ ▄▄ █▀▄█▀▄▀█ ▀█▄████
████▄▄▄▄█▄▄█▀ ▀▄▀ █ ▀ ▄█ ▄▄▄ ▄▄█████
████ ▄▄▄▄▄ ██▀▄ ▀ █ ▄ █ █▄█ ▄█▀█████
████ █ █ █ ▄ ▄▄ ▀ ▀█▀▄▄▄▄▄▄▄██ ████
████ █▄▄▄█ █▀ ▄▄█▄▀█▀▄█ █ ▀ ▄ ████
████▄▄▄▄▄▄▄█▄▄████▄▄▄▄█▄▄▄▄▄█▄███████
█████████████████████████████████████
█████████████████████████████████████
```
and the QR code will display in your terminal.
On your phone, select the option to add a connection via QR code,
and scan it.
You can pipe any data to `qrencode` this way to generate a code,
and it also creates PNG files if you select that format.
## encrypted internet tunnel
I mentioned earlier that to the average user, a VPN is an encrypted tunnel they can run their Internet traffic through.
The topology is
```
Client -> VPN Server -> public internet -> website
```
which makes it look to the website that the traffic originates from the VPN server.
This is actually very similar to our internal network set from before, which boils down to
```
Client -> VPN Server -> public internet -> internal network
```
To achieve the Internet tunnel topology, we just need to modify the client configuration:
```
[Peer]
PublicKey = M/HD4qJYi1RlMH/K9xQ12yW6Cu62LuGasyZhfnVsbUE=
Endpoint = vpn.example.com:51820
- AllowedIPs = 192.168.0.0/24
+ AllowedIPs = 0.0.0.0/0
```
This `[Peer]` section refers to the Server.
We modify `AllowedIPs` so that it includes all possible IPs
(`0.0.0.0/0`),
rather than just the internal subnet's IPs.
With this, every time the Client tries to communicate on the Internet,
for instance it requests `archlinux.org (95.217.163.246)`,
WireGuard will match that IP to the `0.0.0.0/0` (all IPs) mask,
and route the packet to the Server.
Then, the Server will forward that packet to `archlinux.org`.
Thus, we've made a VPN that works just like NordVPN or ProtonVPN, and all the others.
## further reading
This guide distills my knowledge with the network topologies I use,
i.e. connecting to an internal network, and connecting to the Internet via WireGuard.
However, that's of course not the only way you can use a VPN.
Here are some resources that were useful to me:
- man pages `wg-quick(8)` and `wg(8)`
- [Pro Custodibus: WireGuard](https://docs.procustodibus.com/guide/wireguard/)
- [WireGuard website](https://www.wireguard.com/)
Thank you for reading this article!