Introduction: Raspberry Pi4 Firewall

With the new Raspbery Pi 4 (RPi4) just released, I decided to make myself a home-use firewall. After stumbling around on the Internet, I found a great article on the subject by Guillaume Kaddouch (https://networkfilter.blogspot.com/2012/08/building-your-piwall-gateway-firewall.html). The article is amazing, and you should read it before going forward--it will make the process described here easier. The thing is, that article was written in 2012 and is based on the ArchLinux distro. Nothing against ArchLinux, but I wanted to make this using the more common Raspbian build. The RPi4 can handle the processing requirements. So, thank you, Guillaume, for the inspiration!! This instructable will be referencing back to Guillaume's ("GK" for short) original post, you will likely want to have both pages open in your browser.

A couple of key things about my firewall:

  • I have the built-in ethernet jack (eth0) going to the LAN
  • The ISP router is on the TRENDnet adapter (eth1)
  • I have actively disabled the wireless adapter (wlan0)
  • This is not guaranteed to get you 100% there... hopefully at least 99% :) so please provide feedback/comments
  • This is my first instructable. Sorry for anything that does not follow the appropriate instrucable-norms.

Now, let's have some fun...

Supplies

  • Raspberry Pi 4
    • I used the 4GB version, feel free to try a different version
    • Case (I like the FLIRC, but that's your call)
    • Power Adapter
  • MicroSD Card, 32GB or greater (I used a 64GB card)
  • TRENDnet USB3.0 Gigabit Ethernet Dongle (Model: TU3-ETG)
  • A couple RJ45 network cables
  • USB Keyboard and Mouse
  • A Micro-HDMI to HDMI cable (that is plugged into an HDMI monitor)

That keyboard, video, and mouse can removed once you are able to get SSH and VNC up and running.

Step 1: Initial RPi Setup

First thing to do is get your RPi4 up and running as a new system. Download and install the Raspbian full distribution (Raspbian Buster with desktop and recommended software). You will need to reboot a couple times so that it can expand and take advantage of the full MicroSD card.

As it boots, you will need to answer questions about locality, network, keyboard, and mouse. Connect to a network and allow it to update.

Lets also confirm that everything updated properly, and get a couple utilities that may help debugging later:

$ sudo apt-get update
$ sudo apt-get dist-upgrade

$ sudo apt-get install htop
$ sudo apt-get install tcpdump

I did NOT install vim, nor do any of GK's step 8 (configure vim). I just used the vi editor since it has most of those features anyways. This also saved some time and effort.

Once that is complete, lets set the RPi4 up so that we can hot-plug a monitor. My goal was to make it run headless, but if I need to plug in a monitor, it would be recognized.

$ sudo vi /boot/config.txt

In that file:

uncomment (remove the front #-symbol): hdmi_force_hotplug=1

uncomment: hdmi_drive=2

optionally, add: enable_hdmi_sound

Step 2: Networking

If you are following along on GK's site, this is step 3. But keep in mind, I didn't follow a lot of his first steps in exact order.

When I first started this, I connected the RPi directly to my ISP router ("next to my existing network"). This allowed me to play with the configuration without affecting the network. Connect the RPi4 built-in RJ45 to your router (or wireless, if you want). With Raspbian, the easiest way to do this is using the GUI. From the desktop, click the Raspberry Icon > Preferences > Raspberry Pi Configuration. Be sure to enable SSH and VNC. This will install the Real-VNC server client. I found that if you try to connect with the Tight VNC client, it will throw fits and require some additional configuration. So, at this point install the Real-VNC client on your primary desktop/laptop (not your RPi4).

SSH will not work out-of-the-box (GK's step 7). We need to modify some configurations. First, lets modify the ssh config file. Here are the changes I made. Keep in mind that I did not study the impact of every change here. I did what GK's site suggested. Some of these changes may NOT be required.

$ sudo vi /etc/ssh/sshd_config

In that file, uncomment the following lines:

HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
SyslogFacility AUTH
LogLevel INFO
StrictModes yes
PubkeyAuthentication yes
HostBasedAuthentication no

Ignore Rhosts yes

PrintMotd no
PrintLastLog yes
TCPKeepAlive yes

And add the following lines:

Protocol 2
UsePrivilegeSeparation yes
KeyRegenerationInterval 3600
ServerKeyBits 768
RSAAuthentcation yes
RhostsRSAAuthentication no

And modify the following lines:

Port 15507
LoginGraceTime 60
PermitRootLogin no

Let's quickly talk about that first modify...port 15507. SSH normally runs on port 22. GK moved it to 15507--don't know why. You can go either way modify it or not... If you choose to modify it, you will need to add "-p 15507" to any SSH command you attempt to connect with. If you decide to skip it, keep an eye out for the other places that 15507 is mentioned in these instructions and ignore them, particularly the firewall rules!

Lastly for this step, lets get the RPi4's IP address so we know what to connect to:

$ ipconfig -a

Find the active network connect (likely on eth0 or wlan0) and write down that IP address. Now you have what you need to remotely connect to the RPi4. Let's reboot before we go on:

$ sudo reboot

Step 3: Another User

It is best to not use the default RPi username (pi), and you certainly should change the password. To be safe, lets add another user account you can use to remotely connect and continue with (GK's step 6). Back on the RPi, lets add a new user and set permissions for the user to SSH and issue the sudo command:

$ sudo useradd -m -g users -G sudo,netdev -s /bin/bash [USERNAME]
$ sudo passwd [USERNAME]

Feel free to logout or reboot and use that newly created account going forward.

Step 4: Syctl File

Next step is to modify the /etc/sysctl.conf file (GK's step 9). This file is used to change a few kernel settings. We are going to do exactly what GK says to do. Here is a simplified set of steps.

$ sudo vi /etc/sysctl.conf

In that file, uncomment the following lines:

net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.tcp_syncookies=1

net.ipv4.ip_forward = 1

net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.log_martians = 1

And add the following lines:

net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.eth0.accept_redirects = 0
vm.min_free_kbytes = 8192

Restart the service with these new settings and reboot:

$ sudo sysctl -p
$ sudo reboot

Step 5: DHCP & DNS (part 1)

For me, there were two painful parts to this process... Setting up DHCP & DNS, and setting up the firewall rules. So, here we go with the first part. If you are following along on GK's site, we are on step 10.

To do this, you will need a few pieces of information from your ISP router (or current firewall):

  • The internal IP address of the router
  • An IP address you can use for the RPi4's interface to the router
  • The IPs for a nameserver (or two)
  • The interface name for the LAN connection (e.g., eth0 or eth1)
  • The interface name for the ISP connection (e.g., whatever you didn't use for the LAN)

You may also need to modify the router's settings to give the RPi4 a static IP address (bullet 2, above). At least, that's what I did.

First, lets modify the dhcpcd.conf file...

$ sudo vi /etc/dhcpcd.conf

Uncomment these lines:

persistent
option rapid_commit
option domain_name_servers, domain_name, domain_search, host_name
option interface_mtu

For each network interface, you need to set the network details. They should look something like this:

# Static for the interface to the ISP
interface eth1
static ip_address=192.168.1.111/24
static routers=192.168.1.254
static domain_name_servers=8.8.8.8 8.8.4.4
metric 100

# Static for the interface to the LAN
interface eth0
static ip_address=10.210.212.1/24
static routers=10.210.212.1
static domain_name_servers=8.8.8.8 8.8.4.4

#interface wlan0
#static ip_address=10.210.212.152/24
#static routers=10.210.212.1
#static domain_name_servers=8.8.8.8

#Uncomment this section if you want to force an IP address on a device.  The name after 'host'
#is meaningless to the system.  Enter the MAC address of the device as well as the desired
#IP address.  Make sure it is out of the dhcp range.  Repeat as necessary.
#host [ANYTHING] {
#	hardware ethernet xx:xx:xx:xx:xx:xx;
#	fixed-address 10.210.212.250;
#}

Be sure to use numbers that work for you. The IPs above are for my network, with the exception of the name servers which are Google. Notice that I also set the metric for the ISP to 100 to force that to be the default first try for network traffic. I also specifically did nothing to my wireless adapter (wlan0). I intend to completely turn off that interface, so it made sense for me.

Also, if you want to force an IP address on a device (like a NAS), use that bottom section. Give the host a name that is meaningful to you, but know it is never used by anything. Don't forget the semicolons.

Step 6: DHCP & DNS (part 2)

The next step is to modify the dnsmasq.conf file...

$ sudo vi /etc/dnsmasq.conf

We need to uncomment a few lines, and edit a few lines. You will also need to copy a few settings from the dhcpcd.conf file. Two other questions you need to answer for yourself are:

Does the internal LAN (e.g., eth0) need DHCP and DNS?
What DHCP range do you want for your LAN, and how long should each lease be?

Begin by uncommenting a few lines:

bogus-priv
no-dhcp-interface=wlan0
bind-interfaces
dhcp-name-match=set:wpad-ignore,wpad
dhcp-ignore-names=tag:wpad-ignore

Set your name server. Look for the line that starts 'server=' and make it something like 'server=8.8.8.8'.

Set your DHCP range. There are a lot of ways to do this. I chose to provide the two endpoint IPs, the mask, and the length of the lease. My range was 10.210.212.20-10.210.212.240, with a netmask of 255.255.255.0, and lease time of 12 hours. I recommend that you leave some IPs at the top and bottom of your range in case you ever need to give something a static IP.

Set the interface that will get DNS and DHCP (the LAN) by modifying the line 'interface=' to be something like 'interface=eth0). Notice that I specifically told it NOT to assign a DHCP IP address to my wireless network. Again, I intend to completely turn off that interface, so it made sense for me.

Step 7: DHCP & DNS (part 3)

A diversion from GK's instructions for this last step...

When I went to restart my RPi at this point, the dnsmasq process was not active. A little poking around and I found that my eth0 and eth1 network interfaces were not both active before dnsmasq was started, so dnsmasq would fail on start. I would have to connect a keyboard and mouse to the RPi and manually restart dnsmasq. This is not ideal with a headless setup. I read a bunch of posts that said to make various changes to settings (e.g., disable bind-interface) and other things. None of it worked. In the end, I decided to simply write a shell script that would run every 2 minutes and check the status of dnsmasq. If it was not running, start it. I assume that this situation is not unique to me. So, here is what you need to do:

Make the following code into a file called 'dns_masq_keepalive.sh' on your RPi.

#!/bin/bash
# File: dns_masq_keepalive.sh
# August 2019
# Use this with crontab -e (*/2 * * * * /etc/dns_masq_keepalive.sh) to make sure that dnsmasq runs.  The service will stop itself if
# all of the interfaces mentioned in dhcpcd.conf are not up before it begins.  This fixes the problem.
# This next line will return all active jobs with the word 'dnsmasq' in them.  So, don't include 'dnsmasq' in this
# file's name, otherwise it will return it every time and you will never have a restart.

dns_running=$(ps -e | grep dnsmasq)
echo $dns_running
if [ -z "$dns_running" ] then
	#echo No DNSMasq
	sudo /etc/init.d/dnsmasq restart
#else
#echo DNSMasq Running
fi

Cut-and-paste it if you need to. Whatever you do, do not include 'dnsmasq' in the name. The script looks for the word 'dnsmasq' and if the script has it in the name, it will assume that the service is running. Also, rename the file so it ends with '.sh'. Intructables would not let me upload a '.sh' file--which is good. The remaining instructions assume the file exists at: /etc/dns_masq_keepalive.sh.

Second, set permissions on the file so it can be executed:

$ sudo chmod u+x /etc/dns_masq_keepalive.sh

Now we will use the crontab system to make the program run every 2 minutes of every day. Start crontab:

$ sudo crontab -e

It should prompt you to edit using vi or something else. Any will work. Once you can edit it, add the following to the end of the file:

*/2 * * * * sudo /etc/dns_masq_keepalive.sh

No spaces in the '*/2', but spaces between the asterisks. Save and quit. It should tell you that the job is scheduled, or something like that.

Step 8: The Firewall

The next painful process is the firewall (GK's step 11). Raspbian uses the well known iptables system. GK's blog provides three files to help you get there... firewall.simple, firewall.advanced, and firewall.flows. All respect to GK, but make it easy on yourself and just go with firewall.simple. I spent a lot of time trying to figure out the iptables system and rules. I am glad I did, but it was painful. So, I give you the attached two files to help you... firewall.simple and firewall.clear. Copy both of these files to your /etc folder and change the permissions to make them executable:

$ sudo chmod u+x /etc/firewall.simple
$ sudo chmod u+x /etc/firewall.clear

Before you set up any firewall rules, plug a desktop/laptop into your RPi eth0 port and confirm it gets an IP address and has DNS running. The easiest way to do this is to try and ping a generic site and then a known IP address. Also ping your RPi and ISP router. If you get results, then all is good and any network problems you now encounter will likely be a result of firewall issues.

The first file provided originally started as GK's firewall.simple file (thanks, again, GK!). I made a bunch of changes to make it work for this system. It should allow for at least HTTP, HTTPS, DNS, DHCP, ping, internal SSH, internal VNC, and plex. Plex may not have all the open ports for every possible device, but there are a bunch of posts out there to fix that. At the top of the file are values that you will need to change to your network configuration.

The second file, firewall.clear, is intended to be used as you test your firewall rules. When you run 'sudo /etc/firewall.clear' all firewall rules will be cleared and the system should be fully connected to the Internet. So, if you are not able to make a network service (like dns) work with the firewall.simple rules in place, but it begins to work after you run firewall.clear, you know you have a rule problem. This will really only be critical when testing your rules.

So, we have the firewall rules there, we need to make them start when the RPi starts. To do that, we will edit the /etc/rc.local file:

$ sudo vi /etc/rc.local

Once inside add the following to the end of the file:

echo “Loading iptables rules”<br>/etc/firewall.simple >> /dev/null

If you choose to add the snort intrusion detection system, you will need to edit this file again. For now just save it, and reboot.

$ sudo reboot

Step 9: Syslog

Two steps left...

This is an easy one. If you are still there, and following along with GK's blog, this is step 12. You need to do exactly what he says with regard to the syslog file. Here are the abbreviated steps:

Keep 2 months worth of syslog data...

$ sudo vi /etc/logrotate.conf

We need to tell it to use 'one week' as a measurement, and then keep 12 of them. You need the following two lines in this file. I believe you will need to change the existing lines.

weekly
rotate 12

Save it.

Step 10: Intrusion Detection With Snort

The last thing GK configures is the snort system. I recommend this as well. You can follow his rules, and I am not going to copy all of them here, with a few minor modifications. His instructions are for the ArchLinux distro. Here are the few changes for the Raspbian distribution we are using here. The rest of the instructions work fine.

First, do not use sudo pacman -S snort to download and install snort. Do the following:

$ sudo apt-get install snort

Second, you can't verify snort with sudo snort -version. Verify the installation with:

$ sudo snort -V

Finally, to get it to run at startup, do not modify the rc.conf file, edit the rc.local file (again)...

$ sudo vi /etc/rc.local

Add the following lines to the end of the file:

echo “Loading snort”

#/usr/sbin/snort -D -u snort -g snort -c /etc/snort/snort.conf -i eth0 -l /var/log/snort

Now, reboot and it should all magically work.

$ sudo reboot

Step 11: Enjoy

That should be it!

First of all, I cannot thank Guillaume Kaddouch enough! He inspired this.

Second, if you have not already disconnected your keyboard, video, and mouse, you can. Use SSH and VNC to get back in, when needed.

To end, this may not be 100% perfect. Please post back with changes/suggestions/recommendations. My goal would be for this to be the start of discussion and many people enjoying!

Thanks!!

PS... The picture is a RPi4 inside a FLIRC aluminum case with an old Intel fan slightly-modified and zip-tied to the top. There is thermal paste under the fan too, just in case you were wondering. I found something similar on the Internet (https://www.reddit.com/r/raspberry_pi/comments/9bdgrr/it_turns_out_putting_a_heatsink_on_the_flirc_case/) and decided to try it myself.

Step 12: Changelog

As changes are made to this instructable, I will document them here. In case you have a problem, check here to see if you grabbed old instructions or files.

September 25, 2019:

  • Fixed DHCP rules in firewall.simple
  • Fixed DHCP range in instructions (files were correct)
  • Added fixed-IP assignments to DHCP instructions

October 13, 2019

  • Fixed multiple typos
  • Created a second pi so I would have a test SDcard to swap, if needed