The One and the Many

My firewalls

I recently started using a firewall on all my computers. Prior to this I relied on my hosts being behind NATs and so not needing protection from the internet. However I was uncomfortable with trusting all the devices on my local network. I now run firewalls on all my hosts to block everything except what I whitelist.

One reason it took me so long to do this was my dislike of iptables. Many years ago I was very into tweaking firewalls (so much so that there's a running joke with my friends about "ultra-[me]-paranoid" firewalls). I became enamoured of OpenBSD partly for this reason. Its firewall software, pf, was (and is) so far beyond iptables in ease of use that in general I refused to touch iptables because of the contrast. However these days I mainly run Linux (Debian, Raspbian, and Ubuntu), so I decided to get over it.

Alternatives to restrictive firewalls

An alternative solution I was considering was to have hosts I was particularly concerned about restricted to their own local networks. (There are some hosts I'm more concerned about than others, such as those that run internet exposed services). However, barring running multiple routers I was not confident I could isolate the hosts sufficiently.

The setup

My goal was to block all inbound traffic and only allow access to particular ports from particular hosts. To do this:

Once I knew what I wanted to allow, I needed to figure out how to write and load the rules. In particular:

  1. How do I load the rules at boot?
  2. How do I manage the rules from a config file?

I solved both of these by using the iptables-persistent package. When you install it, it offers to save your current rules to /etc/iptables/rules.v4 and /etc/iptables/rules.v6. At boot it loads the rules from these files.

When I change the rules, I run iptables-restore < /etc/iptables/rules.v4 to reload them.

This works well and is not too dissimilar to using pf.conf.

For my desktop, the files look like:


:INPUT DROP [1:32]
:OUTPUT ACCEPT [898:55500]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -s -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p icmp -j ACCEPT
#-A INPUT -j LOG --log-prefix "Blocked packet: " --log-tcp-options



This means:

The line I commented out is useful for debugging. If I enable it, iptables logs all blocked packets to syslog.

The lines immediately following *filter show the policies. The stuff inside the [] is not important (packet counts I believe).

I have similar configs on all hosts, but they vary on which ports they allow. I set up the configs manually on each host. My final step was to ensure backups for each host includes /etc/iptables.

Dynamic rules

In addition to the static rules for each host, I also manage some ports dynamically.

I use iptables-manage to automatically allow any IP connecting to my IRC network to connect to HTTP and SSH (on one host). To do this, a bot updates a list of IPs that iptables-manage monitors. (I discussed this in prior post).

I have one host acting as a jump host for SSH to the others. Hosts only allow SSH from the jump host. The jump host has a dynamic IP, so I use dnsrule to allow IPs associated with a particular DNS record. When the IP changes, hosts allow it once I update the DNS record to reflect the change.

This means I don't have to open any service to the internet except IRC, but I can still provide access to HTTP and SSH. It's a form of port knocking, so it's security through obscurity, but it's better than allowing the internet. I'm reasonably okay with allowing IRC as I wrote the server I use.


I'm fairly happy with this setup. It was simpler to get going than I imagined. I should not have let my distaste for iptables keep me from doing this for so long. After doing this I appreciate iptables more, but I still think pf is far superior.