December 13, 2005

Creating secure wireless access points with OpenBSD and OpenVPN

Author: Manolis Tzanidakis

You know how insecure 802.11x wireless networks are. In this article we'll create an OpenBSD-based secure wireless access point that prevents unauthorized access and encrypts every packet using a VPN tunnel. OpenBSD is one of the most secure operating systems available, is easy to use, and includes almost everything you need for this project in the base installation.

If you are new to OpenBSD, consider reading our review of 3.8, as well as the project's Web site. The same goes for OpenVPN, and check out our recent article too.

OpenBSD supports a wide range of hardware platforms. You don't really need a behemoth for this project; my access point/router runs quite happily on a 166MHz Pentium. A quiet, low-power embedded board such as one from Soekris or the PC Engines WRAP works well. However, if you expect to have lots of clients connected at the same time, consider using a more powerful CPU or a crypto-accelerator card like the ones built by Soekris.

For the wireless part you can either use a wireless adapter support by OpenBSD and have your box run as the actual access point -- which I recommend -- or use a regular access point connected via a crossover UTP cable to an Ethernet interface on the box. If you choose the latter course, keep in mind that most hardware access points use unencrypted Web-based administration interfaces and thus might be vulnerable to attacks.

Configuring a wireless adapter to act as an access point in OpenBSD is a simple matter of creating /etc/hostname.ral0 (replace ral0 with your adapter's interface):

# /etc/hostname.ral0
inet 192.168.2.254 255.255.255.0 NONE media autoselect mediaopt hostap mode 11g nwid my_secure_wlan chan 11

and issuing the command sh /etc/netstart ral0 as root.

If you don't yet have OpenBSD installed, check chapter 4 of OpenBSD's excellent FAQ. For our setup, a minimum installation of bsd, base38.tgz, and etc38.tgz will be fine; feel free to install anything else you might want.

For our VPN we could use OpenBSD's excellent implementation of IPsec (included in the base system), but we'll use OpenVPN instead because it can be deployed easily on both the server and a wide range of clients, including *BSD, Linux, Windows, and Mac OS X. OpenVPN scales well and is secure. The software is already included in OpenBSD's ports and packages repositories, so go ahead and install it.

Authentication and firewall configuration

In order to authenticate wireless clients connecting to our access point we'll use OpenSSH and OpenBSD's packet filter, pf(4), glued together by authpf(8). Start with the SSH daemon configuration by adding the following lines to /etc/ssh/sshd_config:

Protocol 2
ClientAliveInterval 15
ClientAliveCountMax 3
PermitRootLogin no
StrictModes yes
MaxAuthTries 6
AllowUsers YOUR_USERNAMES_SPACE_SEPARATED

Before you start sshd on system startup make sure sshd_flags= exists in your /etc/rc.conf.local file. If sshd is not already running, start it by running sshd as root.

Our access point has three physical network interfaces: ext_if, connected to the Internet (e.g. to a DSL modem); int_if, connected to the wired LAN; and wlan_if, the wireless interface. It also has a virtual interface, tun0, for the VPN. Make sure to replace rl0, pppoe0, and ral0 in the following configuration files with your system's interface names.

Now let's configure our packet filter through /etc/pf.conf. Our setup, as configured below, allows every packet on our wired, VPN, and loopback interfaces to pass through, but only limited incoming SSH traffic (to avoid brute-force attacks) from the wireless interface. It also does NAT on the external interface.

# /etc/pf.conf: pf configuration file.

# macros
ext_if = "pppoe0"
int_if = "rl0"
wlan_if = "ral0"
vpn_if = "tun0"
tcp_flags = "flags S/SA keep state"

# abusers table
table <abusers> persist
# authpf table
table <authpf_users> persist

# traffic normalization
scrub in all

# nat
nat on $ext_if from !($ext_if) -> ($ext_if:0)

# authpf
nat-anchor "authpf/*"
rdr-anchor "authpf/*"
binat-anchor "authpf/*"
anchor "authpf/*"

# block everything by default
block log all
# block everything from abusers table
block log quick from <abusers>

# allow outgoing packets to the internet
pass out on $ext_if proto tcp all flags S/SA modulate state
pass out on $ext_if proto { udp, icmp } all keep state

# wireless interface (allow limited ssh to avoid brute-force attacks)
pass in quick on $wlan_if proto tcp to ($wlan_if) port ssh $tcp_flags (max-src-conn 10, max-src-conn-rate 15/5, overload <abusers> flush global)

# allow everything from wired lan, vpn and loopback
pass quick on { lo, $int_if, $vpn_if }

# antispoof protection for all interfaces
antispoof quick for { lo, $int_if, $wlan_if, $vpn_if }

# End of /etc/pf.conf

Enable packet filter by issuing pfctl -e -f /etc/pf.conf as root. Add the following lines to /etc/rc.conf.local to have it started automatically on system startup:

pf=YES
pf_rules=/etc/pf.conf

For more information on pf, read the pf(4), pf.conf(5), and pfctl(4) man pages, PF User Guide, and Firewalling with OpenBSD's PF packet filter by Peter N. M. Hansteen, which also includes an authpf guide.

Issue the command touch /etc/authpf/authpf.conf to create the empty file required by authpf, then create /etc/authpf/authpf.rules with the following lines:

# /etc/authpf/authpf.rules: firewall rules for authenticated hosts.

# macros
wlan_if = ral0

# allow authenticated hosts to connect to openvpn daemon
pass in quick on $wlan_if proto udp from $user_ip to ($wlan_if) port 1194 keep state

# End of /etc/authpf/authpf.rules

To add a login class for authpf, add these lines to /etc/login.conf:

authpf:\
:shell=/usr/sbin/authpf:\
:tc=default:

Also add /usr/sbin/authpf to /etc/shells to add in the list of acceptable shells; for more info check login.conf(5) and chpass(1) man pages.

Now create users with the adduser command, making sure to set authpf as their shell and login class. Also make sure to add these users to the AllowUsers line in /etc/ssh/sshd_config. If you accept SSH connections over the Internet, add them in USER@WIRELESS_SUBNET format -- e.g. AllowUsers roadwarrior@192.168.2.* -- to tighten things a little bit more.

If everything is working properly, connecting to your access point from a wireless client using OpenSSH or PuTTY will give you access to OpenVPN, which we'll set up now.

VPN Configuration

Before you begin, read OpenVPN's HOWTO.
We'll use OpenVPN in bridged mode, bridging together the wired and VPN interfaces, in order to permit services such as Samba and CUPS to work as if wireless clients were connected on the wired LAN.

After installing OpenVPN from OpenBSD's ports or packages, you must create certificates and keys for the server and the clients. As root, issue the following commands:

# mkdir -p /etc/openvpn/keys
# cp -r /usr/local/share/examples/openvpn/easy-rsa /etc/openvpn
# chown -R root:wheel /etc/openvpn
# chmod 700 /etc/openvpn/keys
# cd /etc/openvpn/easy-rsa
# . ./vars
# ./clean-all
# ./build-ca
# ./build-key-server server
# ./build-key client1
# ./build-key client2 etc.
# ./build-dh
# /usr/local/sbin/openvpn --genkey --secret ta.key
# cd keys
# mv ca.crt dh1024.pem server.crt server.key ta.key /etc/openvpn/keys
# chmod 644 /etc/openvpn/keys/{ca.crt,dh1024.pem,server.crt}
# chmod 600 /etc/openvpn/keys/{server.key,ta.key}

Distribute ca.crt, clientXX.crt, clientXX.key, and ta.key using a secure medium (e.g. SCP or a USB memory stick) to your clients.

Next, create /etc/openvpn/server.conf:

# /etc/openvpn/server.conf: OpenVPN server configuration

daemon openvpn
writepid /var/openvpn/pid
status /var/openvpn/status 10
local 192.168.2.254 # change to your wlan if's IP
port 1194
proto udp
dev tun0
dev-type tap
client-to-client
ca /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/server.crt
key /etc/openvpn/keys/server.key
dh /etc/openvpn/keys/dh1024.pem
server-bridge 192.168.1.254 255.255.255.0 192.168.1.100 192.168.1.120 # change to your setup
ifconfig-pool-persist /var/openvpn/ipp.txt
push "redirect-gateway local def1"
keepalive 10 120
tls-auth /etc/openvpn/keys/ta.key 0
cipher BF-CBC # Blowfish (default)
max-clients 5
user _openvpn
group _openvpn
persist-key
persist-tun
verb 3
mute 20
chroot /var/empty

# End of /etc/openvpn/server.conf

Now issue the following commands to create the user and group _openvpn (which the daemon will run as) and the /var/openvpn directory, and configure the tun0 interface and the bridge between it and the wired interface:

# groupadd -g 500 _openvpn
# useradd -u 500 -g 500 -c 'OpenVPN Server' -s /sbin/nologin -d /var/openvpn -m _openvpn
# echo 'link0 up' > /etc/hostname.tun0
# echo -e 'add rl0\nadd tun0\nup' > /etc/bridgename.bridge0
# sh /etc/netstart tun0
# sh /etc/netstart bridge0

Start the OpenVPN daemon with /usr/local/sbin/openvpn --config /etc/openvpn/server.conf and add the following lines in /etc/rc.local to have it started automatically:

if [ -x /usr/local/sbin/openvpn ]; then
/usr/local/sbin/openvpn --config /etc/openvpn/server.conf
fi

Check /var/log/daemon to make sure the daemon started with no problems.

Now let's configure our first client, on a Linux platform, by creating /etc/openvpn/keys, adding the OpenVPN user and group, copying the keys we created into it, and creating /etc/openvpn/client1.conf:

# /etc/openvpn/client1.conf: OpenVPN client configuration

client
dev tap
proto udp
remote 192.168.2.254 1194 # replace with your access point's IP
resolv-retry infinite
nobind
user openvpn
group openvpn
persist-key
persist-tun
mute-replay-warnings
ca /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/client1.crt
key /etc/openvpn/keys/client1.key
ns-cert-type server
tls-auth /etc/openvpn/keys/ta.key 1
cipher BF-CBC
verb 3
mute 20
chroot /var/empty

# End of /etc/openvpn/client1.conf

Again, check /var/log/messages to make sure everything is OK. If it is, issue openvpn --config /etc/openvpn/client1.conf to connect to the server. Try pinging a host on your wired LAN or browsing the network to make sure the connection actually works.

To verify that everything passing through our wireless client is encrypted, start tcpdump on the wireless and VPN interfaces on the access point. Ping the access point from the client and verify that the output looks like the following:

# tcpdump -env -ttt -i ral0
tcpdump: listening on ral0, link-type EN10MB
Nov 15 21:01:28.865218 0:11:6b:34:91:59 0:e:35:e3:ff:51 0800 223: 192.168.2.254.1194 > 192.168.2.1.32875: udp 181 (ttl 64, id 20205, len 209)

# tcpdump -env -ttt -i tun0
tcpdump: WARNING: tun0: no IPv4 address assigned
tcpdump: listening on tun0, link-type EN10MB
Nov 15 21:05:46.569068 be:88:12:eb:0:4b 0:80:48:1d:e:28 0800 98: 192.168.1.100 > 192.168.1.254: icmp: echo request (id:0926 seq:1) (DF) (ttl 64, id 0, len 84)
Nov 15 21:05:46.569375 0:80:48:1d:e:28 be:88:12:eb:0:4b 0800 98: 192.168.1.254 > 192.168.1.100: icmp: echo reply (id:0926 seq:1) (DF) (ttl 255, id 44123, len 84)

If your client runs OpenBSD, replace dev tap with dev tun0dev-type tap in /etc/openvpn/client1.conf. Windows clients should be the same as Linux, but read the Windows section in the OpenVPN HOWTO.

Go on and enjoy your secure wireless access point!

Click Here!