Chapter 5: Setting Up a Firewall with UFW
A firewall is one of the most important network security tools. It monitors incoming and outgoing packets according to a set of rules. It creates a barrier between your devices and an untrusted network, like the internet.
The term “firewall” comes from building design, being a wall meant to contain a fire from spreading to the rest of a building. In an interesting bit of life imitating art, the movie WarGames is credited with first using the term to apply to computer networks. The actual computer and networking industry soon adopted the term.
Windows and Macs ship with preconfigured firewalls. The Windows Firewall, for example, comes out of the box on a fresh installation of Windows. Windows applications that need to accept incoming packets adjust the firewall rules automatically. The User Account Control that pops up when you open or install certain apps in Windows is often popping up specifically because it needs to adjust Windows Firewall and wants to make sure you have administrator rights to allow it.
Linux doesn't hold your hand like that. You as the user are generally responsible for all firewall policies. This is really important to remember as most Debian-based distributions, like Raspberry Pi OS, do not deny incoming packets by default. All of the ports are open, which can be a huge security vulnerability. If you are using your Pi as a server connected to the internet and haven't configured a firewall, you're potentially leaving, if not a backdoor at least a very appealing window, open for a malicious actor to exploit.
The Linux kernel itself has a packet-filtering firewall built in but is only manipulated through very low-level code. Instead, most (basically all) users use a higher-level application. The default application that comes with Raspberry Pis is called iptables. When Fail2ban detects an intrusion attempt, it bans the offending IP address by creating a rule in iptables.
That being said, iptables is not user friendly and is easy to mess up. Instead, we'll be using an application called UFW, or Uncomplicated Firewall. UFW acts as a wrapper around iptables, which itself is essentially a wrapper around the Linux kernel firewall. We pass commands to UFW, which creates policies in iptables. UFW is available on many Linux distributions and comes included in Ubuntu.
Packets can travel in or out of the Pi. By default, UFW blocks all incoming traffic and allows all outgoing traffic. This is the approach we will take. By banning all packets by default, you will have to create a specific rule in an Allow List (or Whitelist). The alternative is to allow by default and block only if it matches a rule in a Prohibit List (or Blacklist). Using an Allow List and denying by default means no existing incoming connections will be maintained once UFW is enabled unless a rule has been created beforehand allowing the connection. That includes SSH! If you fire up UFW without first creating an SSH rule, your host machine will be locked out. The only way back in would be to connect your Pi to a monitor and keyboard and bring down the firewall.
With our goal of securing the Pi to the greatest extent possible, we'll be using some IP based rules. Most home networks have a subnet mask of 255.255.255.0. That means that the portion of the IP address (called an octet) with a 255 essentially belongs to the network. The 0 refers to a specific device. If your router's IP is 192.168.1.1, then the next device you connect to it will most likely be assigned 192.168.1.2, then 192.168.1.3, etc. Any device that starts with 192.168.1 would be part of your local network. Another way of notating this would be with what's called CIDR notation where the same network would be described by 192.168.1.0/24. The /24 says that the first 24 bits, representing the first three octets, are shared with the last 0 octet referring to a changing value. In the case of a LAN, any device with an IP starting with 192.168.1 would be part of the same network. If it were instead 192.168.0.0/16, that would correspond to a subnet mask of 255.255.0.0, and any device beginning with 192.168 would be part of the same network.
It gets more complicated than that, but that's the basic idea. We're going over this because the very first policy we're going to put in place is allow SSH connections only through devices on the same network. The way we'll specify that is through CIDR notation. Without specifying an IP range, we can open a port to all incoming traffic like so:
sudo ufw allow 80That allows all incoming HTTP packets destined for your Pi to go through the firewall. If you want your Pi to be able to access the internet, that's a rule you'll definitely need, along with 443 to cover HTTPS. For SSH, let's assume we didn't change the default port and it is still 22.
sudo ufw allow 22That will allow incoming SSH attempts from anyone and anywhere. Granted, they'll still need the private key, they'll still need your username, they'll still need your public IP address, and you'll still need to have forwarded port 22 on your router for them to get in. If you've changed from port 22, they'll also need to know what port you're actually using. Not likely, in other words. Still, if we can lock it down further, why not do so?
sudo ufw limit from 192.168.1.0/24 to any port 22 comment 'local ssh'Now to get in, having your public IP address isn't enough, even if you've forwarded port 22 on your router. To successfully start a connection, you have to be on the same network. Most often, that means being connected to the same Wi-Fi network or plugged into the same router. We add an optional comment to remind us when scanning our rules what this one is supposed to do. Part of the danger of messing with firewall rules is leaving rules in place that are outdated. Commenting all of your UFW rules is a good habit to get into that can remind you to delete or update and outdated rule. We also use the word “limit” rather than “allow” to act as a further bottleneck. Fail2ban should still kick out offending IP addresses, but if your Pi should get swarmed by multiple IPs at once, UFW will close the port.
Now that we know why firewalls are important, what the IP addresses in our rules represent, and the application we will be using, we can proceed with installing and configuring UFW. Fortunately, it's extremely simple.
Configuring UFW
sudo apt install ufwsudo ufw allow from <local IP CIDR> to any port <port #> comment 'local ssh'Replace <local IP CIDR> with the appropriate value. If on a GLi.Net router, for example, replace it with 192.168.8.0/24.
Replace <port> with the SSH port you chose in Chapter 3.
sudo ufw allow 80 comment 'http'sudo ufw allow 443 comment 'https'sudo ufw limit from 192.168.8.0/24 comment 'local ssh'sudo ufw enablesudo ufw statussudo ufw status numberedsudo ufw delete 1sudo ufw disable Followed by
sudo ufw enableOr just type
sudo ufw reloadNow we're not at just two factor authentication; we're at multifactor authentication. We have something we know (the SSH key passphrase), something we have (the SSH key), and someplace we are (on a local network). But what if we still want to be able to SSH remotely when we're not on the same network? We'll be able to do that through a VPN, covered in Chapter 10.
