blob: cf4103448ef1ddf4766b9bab688583745f3375f7 (
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
149
150
151
152
153
154
155
156
157
158
|
#+TITLE: Creating an OpenBSD Wireguard VPN Gateway
#+DATE: 2023-12-29T16:35:36-05:00
#+DRAFT: false
#+DESCRIPTION: Creating an OpenBSD wireguard VPN gateway using only the base system.
#+TAGS[]: openbsd wireguard vpn
#+KEYWORDS[]: openbsd wireguard vpn
#+SLUG:
#+SUMMARY:
A couple of years ago I published a [[{{<ref "/posts/openbsd-vpn-gateway" >}}][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.
I'll only be highlighting the things I've changed since the last setup
in this post, so please refer to the previous post for more details.
One advantage this iteration has over the previous one is that it no
longer requires third party software to be installed on the OpenBSD
router. Everything required comes as part of the base system. We will
also be taking advantage of routing tables to restrict what we send
through the VPN.
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.
Here's a diagram of what we're building.
[[file:diagram.png]]
Unlike the previous setup, in this version we're going to create a
separate routing table for the VPN. This affords us a lot of
flexibility, as we can be very explicit how we route our traffic. In
this setup, only packets coming in one interface with a source
address on the local network will be sent through, as opposed to all
traffic leaving the router. We can also selectively send traffic from
the router through the VPN using the =route(8)= command.
#+begin_src
route -T <rtable> exec <program>
#+end_src
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=
Now all traffic coming from the network through this router should be
NAT-ed and sent over the VPN.
|