GitHub
Update from November 25, 2022 ¶
I switched to classic wg-quick from this Ansible setup. The solution turned out to be technically much simpler, and it meets my functional needs.
Update from March 21, 2022 ¶
I removed automatic key generation due to excessive implementation complexity and the inability to configure servers individually; it could only be done collectively. Now it’s different. Configuration changes are incompatible with the old version; please check the examples in the repository, as I have added documentation there.
Introduction ¶
Wireguard is an amazing, simple VPN that is already included in the kernel. It’s a very powerful and straightforward tool. It installs much more easily than OpenVPN.
Problem Statement ¶
- I live in Ukraine, and unfortunately, some internet resources are blocked, and access is sometimes necessary.
- I want to be able to remotely connect from my phone or someone else’s computer to my home server.
- I want traffic to be proxied on the PC only for domains on the list, not everything.
With these requests, I started working. For myself, all for myself. No one else needs it for now, although I posted it on GitHub. Posted is a strong word. No documentation, nothing! Yes! Just lazy to write… Oh well.
Solution Overview ¶
I used Ansible, which can configure the network immediately. What it can do:
- Use a constant preshared key for all connections.
- Ability to specify your AllowedIPs.
- Ability to add peers with statically defined keys (since Ansible can’t be run on the phone).
- Automatic selection of free rt_table IDs and fwmarks.
- Ability to set up more than one network on the same machines with a single run!
What it can’t do:
- Separate runs on one or a subgroup of hosts. Only all at once.
Understanding Ansible Variables ¶
all.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| # CIDR networks
wireguard_networks:
home: 192.168.99.0/24
vpn: 10.0.0.0/24
# marks for routing - each network has its own
wireguard_fwmarks:
home: 1000
vpn: 2000
# preshared key for the network
wireguard_preshared_keys:
home: longalphanum
vpn: longalphanum
# keys for each server in each network where it exists
wireguard_keys:
home:
public_host:
public: longalphanum
private: longalphanum
home_server:
public: longalphanum
private: longalphanum
laptop:
public: longalphanum
private: longalphanum
vpn:
public_host:
public: longalphanum
private: longalphanum
laptop:
public: longalphanum
private: longalphanum
smartphone:
# only public key for the phone, as the private is not needed here
public: longalphanum
wireguard_addresses:
# addresses for each server in each network
home:
public_host: 192.168.99.1
laptop: 192.168.99.2
home_server: 192.168.99.3
vpn:
public_host: 10.0.0.1
laptop: 10.0.0.2
smartphone: 10.0.0.3
|
We have a server with a public IP, and it will serve as the connection point for everyone. The topology is a star.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| # these are variables for a specific host - the server
wireguard_enabled: yes
# wireguard-tools have already been installed, so we turn this off
wireguard_install_tools: no
# list of peers
wireguard_peers:
# for the home network
home:
# the first peer is our laptop, where we run Ansible
- name: laptop
# the second has inventory_hostname home_server
- name: home_server
# and keep the network so that it can always be reached,
# because the server is behind NAT
keepalive_seconds: 25
# the second network will only be used from the phone and PC
vpn:
- name: laptop
# and here is the phone
- name: smartphone
# now we'll choose the ports it should listen on
# for the others, it doesn't matter, but
# for the server, it's better to specify everything statically
# so nothing is regenerated if anything happens
# and all clients don't need to be reconfigured
# with new server credentials
wireguard_ports:
home: 2000
vpn: 3000
# enable masquerading so that the server can
# release peer traffic to the Internet as a gateway
wireguard_masquarade:
vpn: auto
|
Now, let’s specify variables for the laptop we are working on.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # and again explicitly enable the role
wireguard_enabled: yes
wireguard_peers:
vpn:
# in the VPN network peers, only our public server
- hostname: public_host
# we have a domain pointing to the server so we don’t need to remember the address
endpoint: vpn.example.com
# routing all traffic through the VPN, and it's important to understand
# that we are not sending all system traffic through Wireguard
# we are telling Wireguard on the host to allow
# everything that specifically comes to you on the output and does not match
# in other networks by more narrow rules
allowed_ips:
- 0.0.0.0/0
- ::0
keepalive_seconds: 25
home:
# the contact point for the home network is the same
- hostname: public_host
endpoint: vpn.example.com
keepalive_seconds: 25
allowed_ips:
# and in the paths, we specify only the subnet of the home network
- "{{ wireguard_networks.home }}"
# and enable proxying specific domains through the VPN
wireguard_proxy_domains:
vpn:
list:
- site.we.want.access
- another.one
|
And the home server just needs to be accessible. It doesn’t need to be in the VPN.
1
2
3
4
5
6
7
8
9
| # here I hope everything is already clear
wireguard_enabled: yes
wireguard_peers:
home:
- hostname: public_host
endpoint: vpn.example.com
allowed_ips:
- "{{ wireguard_networks.home }}"
keepalive_seconds: 25
|
Conclusion ¶
From any peer, there is access to any within the network. Traffic finds its way through the public server. Everything works. I measured with iperf; out of 80 Mbps, Wireguard delivers ~60 - to me, that’s an excellent result!