blob: 3f27707e193820272eef4eef0492de2b0aedae05 (
plain) (
blame)
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
#+TITLE: OpenBSD Wireguard VPN Gateway
#+DATE: 2023-12-29T16:35:36-05:00
#+DRAFT: true
#+DESCRIPTION:
#+TAGS[]: openbsd wireguard vpn
#+KEYWORDS[]: openbsd wireguard vpn
#+SLUG:
#+SUMMARY:
A couple of years ago I published a (link) blog post about creating an
OpenBSD VPN gateway using OpenVPN.
I've recently switched from an OpenVPN-based VPN provider to one that
uses Wireguard. As a result I've had to redo my VPN gateway.
One advantage this iteration has over my previous setup is that it no
longer requires third party software to be installed on the OpenBSD
router. Everything required comes as part of the base install.
The purpose of the VPN gateway is to allow any device on the network
to send its traffic through a VPN without installing anything. Instead
of installing one profile per device, the client just sets the VPN
Gateway as its default route.
Unlike the previous setup, in this version we're going to create a
separate routing table for the VPN. This lets us set the VPN as the
default route for the traffic we want to go through, while leaving the
rest of the system unaffected. It also lets us selectively send
traffic from the router through the VPN using the =route(8)= command.
#+begin_src
route -T <rtable> exec <program>
#+end_src
Here's a diagram of what we're building.
(diagram of the network configuration)
The first step in the process is getting the VPN profile from the VPN
provider. It should look something like the following.
#+CAPTION: profile.conf
#+begin_src conf
[Interface]
PrivateKey = PRIVATEKEY
Address = XX.XX.XX.XX/32,YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY/128
DNS = ZZ.ZZ.ZZ.ZZ
[Peer]
PublicKey = PUBLICKEY
AllowedIPs = 0.0.0.0/0,::0/0
Endpoint = ENDPOINT:51820
#+end_src
We then have to rewrite it into OpenBSD's =hostname.if(5)= format.
We'll call it =/etc/hostname.wg0= to create a Wireguard interface and
execute the following commands when it's created.
#+CAPTION: /etc/hostname.wg0
#+begin_src conf
inet XX.XX.XX.XX/32
inet6 YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY/128
wgkey PRIVATEKEY
wgpeer PUBLICKEY wgaip 0.0.0.0/0 wgaip ::0/0 wgendpoint ENDPOINT 51820
#+end_src
In our setup, since we want to setup a routing table where the VPN is
the default route, we need to create it and set the routes
accordingly. We can do this by adding commands to the end of our
config file. Lines beginning with =!= are commands that are run as
root when the interface is being created. In this case our new routing
table (rtable) will be number 1. The default routing table is number 0.
#+CAPTION: /etc/hostname.wg0
#+begin_src conf
inet XX.XX.XX.XX/32
inet6 YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY/128
wgkey PRIVATEKEY
wgpeer PUBLICKEY wgaip 0.0.0.0/0 wgaip ::0/0 wgendpoint ENDPOINT 51820
!route -T 1 add -inet default XX.XX.XX.XX
!route -T 1 add -inet6 default YYYY:YYYY:YYYY:YYYY:YYYY:YYYY:YYYY
#+end_src
Now that our interfaces are setup, we need to create the firewall
rules that will take care of the routing and NAT. We use a couple
macros here (=$ext_if= and =$vpn_if=) to make it easy to change the
interface names if we ever have to.
We can bring up the interface using the command =sh /etc/netstart wg0=.
#+CAPTION: /etc/pf.conf
#+begin_src conf
set skip on lo
block return # block stateless traffic
# pass # establish keep-state
ext_if = "vio0"
vpn_if = "wg0"
# Don't send traffic for us (ssh) through the VPN
pass in quick on $ext_if proto tcp from $ext_if:network to self port 22
pass in on $ext_if from $ext_if:network rtable 1
pass out on $ext_if from self
match out on $vpn_if from $ext_if:network to any nat-to $vpn_if
pass out on $vpn_if
#+end_src
Let's break down this file line by line.
- =set skip on lo= This is part of the default =pf.conf(5)= file. It
stops =pf= from evaluating traffic on the loopback interfaces. This
is fine.
- =block return= Block all traffic by default
- =# pass= We comment out the =pass= rule. This is part of the default
configuration to allow all traffic to pass in and out. We want to
only allow traffic we explicitly specify through so we remove it.
- =ext_if = "vio0"= Create a macro for the main egress interface. This
interface will be connected to our network and also have access to
the internet.
- =vpn_if = "wg0"= Create a macro for the VPN interface.
- =pass in quick on $ext_if proto tcp from $ext_if:network to self
port 22= Here we allow any traffic directly addressing our server on
TDP port 22 to pass in without any further rule evaluations. This
lets us SSH into our server without the packets being put into the
VPN routing table.
- =pass out on $ext_if from self= This lets us connect to the internet
from the VPN server.
- =match out on $vpn_if from $ext_if:network to any nat-to $vpn_if=
This rule will take any traffic coming in from out network and NAT
it to our VPN interface.
- =pass out on $vpn_if= This lets the traffic out through the VPN
interface.
We can apply the file without rebooting with the command =pfctl -f /etc/pf.conf=
Finally we need to make sure our machine will forward traffic. We can
do this by adding a line to our =sysctl.conf(5)= file.
#+CAPTION: /etc/sysctl.conf
#+begin_src conf
net.inet.ip.forwarding=1
#+end_src
We can change the variable without rebooting with the command =sysctl net.inet.ip.forwarding=1=
|