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
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.
Attachments
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
19 Comments
1 year ago
Hey, on the dnsmasq you don't need to run a bash every 2 minutes with cron. Edit /lib/systemd/system/dnsmasq.service. Change Requires and After in the conf to Requires=network-online.target
After=network-online.target. This will change it to dnsmasq requires network interface to be online and you wont get the error anymore.
Question 1 year ago
Can anyone provide the throughput loss with this setup? Currently residing in a rural area with at most 35 Mbps (on a good day). Would hate to put in the money, time and effort only to cripple my network.
Thank you in advance for the assistance.
Question 1 year ago
Hi, how get you load the firewall rules from /etc/firewall.simple or advanced into the system so they available there? if i run iptables -S its always says INPUT FORWARD OUTPUT policy for each ACCEPT
how could i check if the rules are working??
And step 8
$ sudo chmod u+x /etc/firewall.simple
$ sudo chmod u+x /etc/firewall.clear
sudo /etc/firewall.clear nothing happen here, just get could not bla same by .simple
Question 2 years ago
I don't know much about Linux,
Why I am getting this error I doing exactly what the article is saying.
sysctl: cannot stat /proc/sys/net/ipv4/conf/eth0/accept_redirect: No such file or directory. Thanks,
Question 2 years ago on Step 11
hello, thanks for the great work. I tried myself. got connection but no internet connection. Any idea?
Answer 2 years ago
Solved. mac address eth0 and eth1 were changed. Everything is working fine now.
2 years ago
Could someone share the kind of throughput you are able to achieve using Pi4 FW using USB3.0 1G Ethernet Dongle & Built-in Ethernet port.
2 years ago
FYI - I lost a lot of time between steps 7 and 8 trying to get clients on eth0 talking to the web as I'd forgotten to add a static route on the ISP router, so traffic was getting there OK through the Pi, but ISP router didn't know how to send traffic back.
3 years ago
Has anyone use the Pi Hat with the ethernet on it? That way you have 2 bus tied ethernet adapters.
Reply 2 years ago
Do you mean the official Raspberry Pi PoE HAT? Or another one?
3 years ago
Hi TexasMouse , really nice tut. Thnks for sharing . Is there any chance to find some time for a video tut ? I am on a PPPoE with my ISP , will it work ?Thank you
Reply 3 years ago
Unfortunately, I don’t have the time for a video tutorial. These are surprisingly difficult to well and take a lot of effort and time (at least for me)!!
Since my ISP does not use PPPoE, I can’t help with that. I would imagine that it would be very possible. I would look for instructions on how to just get a Pi up and running using PPPoE and merge that with these instructions. If you do figure it out, please let us know. I am sure there are others that want to do the same.
Question 3 years ago
Is there a version that allows you to use your ISP connection (eth1) via DHCP instead of static?
Reply 3 years ago
I would imagine this could be done with some changes to the dhcpcd.conf and dnsmasq.conf files (step 5 and 6). I would try not adding the static lines to the dhcpcd.conf file and then not putting the interface into the ‘no-dhcp-interface=‘ line in dnsmasq.donf. Likely more will need to be done, but that should be the bulk of it. I got around this issue by configuring my ISP router to always issue the same IP, and then coding it into the Pi as static.
3 years ago on Step 7
Hi TexasMouse,
Thanks for your tuto.
I'm trying to follow it but I've a doubt about the file "dns_masq_keepalive.sh.txt" you provided by the external link at the end of step 7 => Error of file maybe, it doesn't look like a bash script :
# ImageMagick pixel enumeration: 612,792,65535,srgba
0,0: (65535,65535,65535,65535) #FFFFFFFFFFFFFFFF white
1,0: (65535,65535,65535,65535) #FFFFFFFFFFFFFFFF white
...
Other concern : dnsmasq.conf doesn't exist by default on Raspbian Buster.
You need to install first the package "dnsmasq" before be able to edit "dnsmasq.conf"
Waiting for the bash file, thanks by advance !
Bye !
Reply 3 years ago
You are correct... the problem you mentioned with dnsmasq not existing seems to be a result of me first doing this with an older version that had it installed by default. I have adjusted my instructions to now add the install step. The problem with the keepalive file I could not easily fix. Every time I tried to make it downloadable to everyone, it corrupted the file. Not sure why, but I was probably doing something wrong. I ended up correcting this by making it a code snipet you could cut-and-paste. I hope this helps.
Thanks!!
Reply 3 years ago
I tried multiple times to upload the simple text file with the same results you are seeing. I decided to simply add it as in-line code. Please just cut and paste it into a new file called /etc/dns_masq_keepalive.sh.
As for not having the dnsmasq package, I can only guess... It could be that the variant you are using does not include dnsmaq and you will need to install it with 'sudo apt-get install dnsmasq'. I used the full desktop version that included recommended software. Maybe that will help.
Thanks!!
3 years ago
Great post. Thanks. I have built the same setup, Rpi-4 4gig, and use it as my firewall. My provider is Comcast/Xfinity. Comcast has native IPv6. My LAN ip address is static and I use dhcpcd to get my address from Comcast. I turned my wifi interface off in "raspi-config" so all wlan problem/configuration disappeared. I added "arno-iptables-firewall" as my firewall which has about 300+ statements for ip4/ip6 each.
The IPv6 setup was a challenge. The dhcpcd manual pages and examples are crap. The key for (dhcpcd.conf) IPv6 are:
interface eth0
ipv4
ipv6
dhcp
dhcp6
# default IPv6 route
ia_na 1 eth0 # request an IPv6 address
ia_pd 1/::/64 eth0/0/64 # request a PD and assign it to eth2
Especially important are the last 2 lines which get a IPv6 /64 address for my home network. I haven't found an easy hook in the dhcpcd daemon to get Comcast's /64 part of the address. I use dnsmasq to get give out IPv4 and IPv6 address to the various devices I have on my network. I use /etc/hosts to set static IP's for those hosts I need a name for. I enable "enable-ra" to hand out SLAAC addresses for IPv6 which uses by /64 address space minus 500 addresses I reserve for static. Unfortunately, getting the IPv6 address during boot is a kinda chicken-and-the-egg problem, I have to wait until the boot is over to run a script that parses the IPv6 address and then transforms the address into a dnsmasq statement. And after a restart, the /64 address is advertised into my network.
My script is:
LAN="eth0" #inside lan device
base_prefix="$(/sbin/ip -6 ad sh ${LAN} | grep 'scope global dynamic' | cut -d ' ' -f 6 | cut -d : -f 1-4 | sed 's/00$//')"
if [ "$base_prefix" = "" ]; then
exit 0
fi
# following sets up router advertizements for dnsmasq
# ie: dnsmasq reads this file to get the ipv6 network base prefix numbers
echo "dhcp-range="$base_prefix":0:0:0:500,"$base_prefix":FFFF:FFFF:FFFF:FFFF, ra-names, 150" >/etc/dnsmasq.d/router-advertise.conf
/etc/init.d/dnsmasq restart
exit 0
Dnsmasq can read a configuration statement from a directory. BUT EVERY machine needs a good ipv4 and ipv6 firewall because raw ipv6 addresses are visible through the firewall.
Again thanks for this article.
Reply 3 years ago
Love this! Glad you are able to build upon it!!!