Not in fact any relation to the famous large Greek meal of the same name.

Tuesday 7 February 2023

Network firewall on a Raspberry Pi using OpenWRT

Previously on #homelab:

     
For a while I’ve been using a TP-Link TL-R860 router as the main firewall and switch for my home ethernet network. This has worked well and reliably (unlike the Linksys WRT54-G which I eventually put on a Christmas lights timer to reboot it every night), but it’s old now and is no longer getting security updates (if it ever did). Its HTTPS configuration UI understands nothing newer than SSL3.0, and I wouldn’t even know where to find a browser prepared to lower itself to that these days. As it’s the final line of defence for my home network, I wanted to upgrade to something a bit newer.

I’ve still got a handful of Raspberry Pi 3 devices knocking around, so I thought I’d press one of them into service as the new ethernet-to-ethernet firewall – plus, OpenWRT is open-source and widely used, and so probably more trustworthy than a proprietary router that’s (searches “Amazon” folder in Evolution) over ten years old now. And gigabit switches are cheap these days, which might provide a handy speedup, at least for internal traffic, from the 10/100 switch in the TP-Link. That means that the goal is for the network to look like this:

So here’s a complete list of parts:

  • Raspberry Pi 3B (or a Raspberry Pi 4 if you can get one)
  • Branded Raspberry Pi power supply
  • A USB flash drive to be the root filesystem (it doesn’t need to be high-performance; I used a Sandisk one which is literally “Amazon’s Choice” for the search “flash drive”)
  • A USB ethernet adaptor supported by Linux (I used an Amazon Basics one)
  • A gigabit ethernet switch (any one will do; I picked a Zyxel one mostly because I wanted one with the LEDs on the front and the sockets on the back)

Just as when setting up a home server, if you’re using a Raspberry Pi 3 you’ll need to enable boot-from-USB; the process is described in the linked post and you’ll need to boot once from a microSD card to be able to make the configuration change. The Raspberry Pi 4, though, can boot from USB out-of-the-box.

Alternatively, you could just stick with a microSD card for the whole process, but it’s persistently rumoured that running a Raspberry Pi from a microSD card is bad for long-term reliability, and that running from USB is better.

This blog post will cover:

  1. Installing OpenWRT
  2. Adding a second ethernet interface via USB
  3. The security setup
  4. Conclusion

The security setup will, as in previous posts, follow the principle of minimising potential attack surface. This post is as much about writing down for my own benefit what I did here, as it is for any wider purpose!

1. Installing OpenWRT

OpenWRT is at heart just a very custom Linux distribution, and it’s installed onto the Raspberry Pi like any other Linux. The OpenWRT folks have a specific page on using the Raspberry Pi. Download the latest “Firmware OpenWRT Install” img.gz file from the Installation section there; when I did it, that was version 22.03.3.

Install it on your flash drive (or microSD card) using rpi-imager (on Ubuntu, sudo apt install rpi-imager). Click “Choose OS” then “Use custom” then select the img.gz file you just downloaded. The click “Choose storage” and select your USB flash drive (or microSD card); be careful because other disks that you don’t want wiped may also be listed. Then click “Write”.

The default image contains a boot partition that assumes that the root filesystem will be on the microSD card (/dev/mmcblk0p2); if you’re using a USB flash drive then that’s not going to work. So in that case you need to mount the boot partition from the flash drive, and edit cmdline.txt to change the part of the command-line that specifies the root partition. Find the part that says root=/dev/mmcblk0p2 and change it to root=/dev/sda2. Don’t alter the rest of the command-line. Unmount the partition and unplug the flash drive.

With that flash drive, your Raspberry Pi is now able to run OpenWRT. Initially the new OpenWRT install will set itself up with IP address 192.168.1.1 on the Raspberry Pi ethernet interface; you can connect to this directly with an ethernet cable from a laptop (or desktop), setting the laptop’s IP address to 192.168.1.2. You can then use SSH from the laptop to log into the Raspberry Pi:

ssh root@192.168.1.1

Now would be a good time, as OpenWRT itself now suggests, to change the root password from the default blank one.

The Raspberry Pi isn’t yet a very useful firewall, though, because it only has the one ethernet port. It does additionally have wifi, but that’s not enabled by default and anyway I’m specifically after an ethernet-to-ethernet firewall here. So it’s time to add the second ethernet interface.

2. Adding a second ethernet interface via USB

The goal is for the Raspberry Pi’s built-in ethernet to be the inside of the firewall, connected to the switch, and for the USB ethernet to be the outside, connected to my ISP’s router.

Parts of this section are based on Vladimír Záhradník’s article on Medium (also here) – but, like me, you’ll need to go off-piste from that script a little if it’s awkward to connect the firewall to the internet straightaway.

And it’s likely that the firewall won’t be on the internet straightaway, because although the laptop can use SSH or HTTP to talk to the Raspberry Pi, the laptop probably won’t be set up to forward packets, so the Raspberry Pi itself will not be able to access the internet.

The first thing to do is work out what Linux driver is needed for your chosen USB etthernet adaptor; the easiest way to do that, is just plug it in to an existing Linux box and see what module gets auto-loaded; in my case it was ax88179_178a.

You need to find the equivalent driver package starting from downloads.openwrt.org: I clicked on “OpenWRT 22.03.3” then “bcm27xx” then “bcm2710” (that’s for Raspberry Pi 3, for a 4 it would be “bcm2711”), then “packages”, then I looked along the packages starting with “kmod-usb-net” until I found one with “ax88179” in the name: this turned out to be kmod-usb-net-asix-ax88179_5.10.161-1_aarch64_cortex-a53.ipk.

Now from a fully-operational OpenWRT install, I could just install that using “opkg install kmod-usb-net-asix-ax88179” and it would fetch and install it directly; for this initial setup, though, I had to download it manually onto the laptop, then scp it to the firewall, then install locally:

on the laptop
scp kmod-usb-net-asix-ax88179_5.10.161-1_aarch64_cortex-a53.ipk root@192.168.1.1:
ssh root@192.168.1.1
now on the Pi
opkg install ./kmod-usb-net-asix-ax88179_5.10.161-1_aarch64_cortex-a53.ipk

And the opkg command failed: it needed some further modules, some dependencies. An online opkg would fetch and install dependencies automatically, but I had to do that manually, by repeating the download-and-scp process for the following:

  • kmod-libphy_5.10.161-1_aarch64_cortex-a53.ipk
  • kmod-mii_5.10.161-1_aarch64_cortex-a53.ipk
  • kmod-usb-net_5.10.161-1_aarch64_cortex-a53.ipk

Once those were installed, the main ax88179 package could also be successfully installed.

When I did this, the ethernet adaptor was still not being detected, and it took me far too long to realise that it was still plugged into the laptop from when I was working out what driver it needed. As soon as I plugged it back into the Raspberry Pi, it worked fine and appeared in OpenWRT’s web UI as eth1 (with eth0 being the built-in ethernet).

3. The security setup

Almost everything else can now be configured from the web UI. Under the “Network” menu, choose “Interfaces”, then “Add new interface”, call it “wan”, choose “DHCP client”, and assign it to eth1.

Under “Network” and “Firewall” the default will probably already be correct: LAN-to-WAN traffic gets forwarded, WAN-to-LAN traffic gets rejected except that masquerading (i.e., NAT) is enabled.

Under “System” and “Administration” you can set up SSH access: have it listen on the LAN interface only and give it your SSH public key. Once you’re sure that SSH public-key authentication is working, you can disable SSH password authentication. (In the worst-case scenario, you can still log in to the firewall on console, using a keyboard and monitor.)

Under “HTTP(S) Access”, make sure “Redirect to HTTPS” is turned off; this is less secure but we’ll be re-securing it a bit later.

Back under “Network” and “Interfaces”, choose “Edit” for the LAN interface and you can set the IP address and configure the DHCP server. You get a warning message when changing the settings, because that’s the interface from which you’re using the web UI, but if you’re confident in your settings then make the change; worst case you might need to unplug and re-plug the network cable in order for the laptop to realise it needs to renegotiate its own IP address on ethernet.

You can now connect the firewall’s WAN interface to the upstream (ISP) router and use SSH to log in to the firewall (via its LAN interface) under its final LAN IP address. The firewall now has internet connectivity via the upstream router, so now is a good time to log in and install any additional packages you might need:

opkg install nano

Now is also a good time to set up anything required on the upstream router – for instance a static DHCP address, or any port forwarding.

I made one final security-related configuration change. By default the web UI listens on all interfaces, on both IPv4 and IPv6. That’s not terrible, because a further login is still required to actually do anything, and because firewall rules on the WAN interface will drop connection requests anyway. But just to reduce attack surface, I changed that to only listen for connections from localhost. To do that, SSH into the firewall and edit /etc/config/httpd. As first installed, it will have lines a bit like these:

/etc/config/uhttpd
...
	list listen_http	0.0.0.0:80
	list listen_http	[::]:80
	list listen_https	0.0.0.0:443
        list listen_https	[::]:443
...

And to change it to listen on localhost only, replace those four lines with this one (leaving the rest of the file unchanged):

/etc/config/uhttpd
...
        list listen_http '127.0.0.1:80'
...

Now the only way to access the web UI is via SSH tunnelling – in other words, only an attacker with SSH credentials can change anything. So I use a script to run the SSH command, in order to avoid remembering the command parameters (“donk” is the hostname of the firewall):

ssh-donk
#!/bin/bash

# 8001: openwrt

exec ssh -t \
     -L 8001:localhost:80 \
     root@donk

And with that running on the laptop, the OpenWRT web UI is accessible in the laptop’s web browser as http://localhost:8001.

4. Conclusion

Setting up a Raspberry Pi 3 as a firewall was relatively straightforward. OpenWRT is well-supported and gets ongoing security maintenance, and is much less likely to contain unexpected backdoors (or even bugs) than an ancient proprietary firewall. And its performance is good – or at least, better than the dusty old TP-Link thing. From inside the firewall, my laptop now gets 94Mbits/s download, 20Mbits/s upload – up from 28 down and 8 up when using the previous firewall. Quite possibly a Raspberry Pi 4 could do even better; 94Mbits/s is very close to saturating the Raspberry Pi 3’s onboard 10/100 ethernet interface, whereas the Raspberry Pi 4 has gigabit ethernet onboard.

About Me

Cambridge, United Kingdom
Waits for audience applause ... not a sossinge.
CC0 To the extent possible under law, the author of this work has waived all copyright and related or neighboring rights to this work.