Making firewalling less of a headache
I recently spent half an afternoon translating the firewalling rules of a server from some hand-knitted maintenance script1) into a better structured ferm script. “What is this ferm” you might ask, and indeed I have never mentioned this tiny little tool so far which has helped me writing firewall rulesets now since I discovered it some months ago thanks to bzed. I think it's about time to change that
So first of all, a definition, and I think I'll just quote the website here as IMHO that already expresses perfectly well what ferm2) is all about:
ferm is a tool to maintain complex firewalls, without having the trouble to rewrite the complex rules over and over again. ferm allows the entire firewall rule set to be stored in a separate file, and to be loaded with one command. The firewall configuration resembles structured programming-like language, which can contain levels and lists.
So basically, what ferm does is provide you with a DSL for defining iptables rules. The features of that small language include variables (e.g. for IPs), lists (e.g. for a set of IPs, subnets, interfaces or ports), the ability to define functions (e.g. to forward ports) and to nest definitions.
A small example of what ferm can do? Take this (totally senseless) script:
@def $SOME_IP = 192.168.23.42;
@def &TCP_TUNNEL($port, $dest) = {
table filter chain FORWARD interface ppp0 proto tcp dport $port daddr $dest outerface eth0 ACCEPT;
table nat chain PREROUTING interface ppp0 proto tcp dport $port daddr 1.2.3.4 DNAT to $dest;
}
&TCP_TUNNEL(http, 192.168.23.33);
&TCP_TUNNEL(ftp, 192.168.23.30);
&TCP_TUNNEL((ssh smtp), $SOME_IP);
domain (ip ip6) chain INPUT {
proto tcp {
ACCEPT dport (ssh http ftp);
ACCEPT dport 1024:65535 ! syn;
jump MYCHAIN proto tcp saddr $SOME_IP {
daddr google.com dport 80;
dport 23;
}
DROP;
}
}
chain MYCHAIN {
RETURN;
}
Once you get used to the syntax (which is explained to great detail in the manual/manpage and IMHO quite intuitive), it gets easy and fast to both write and understand such scripts. The translated iptables output on the other hand…
/sbin/iptables -t nat -P PREROUTING ACCEPT /sbin/iptables -t nat -F /sbin/iptables -t nat -X /sbin/iptables -t filter -P FORWARD ACCEPT /sbin/iptables -t filter -P INPUT ACCEPT /sbin/iptables -t filter -F /sbin/iptables -t filter -X /sbin/iptables -t filter -A FORWARD -d 192.168.23.33 -i ppp0 -o eth0 -p tcp -m tcp --dport http -j ACCEPT /sbin/iptables -t nat -A PREROUTING -d 1.2.3.4 -i ppp0 -p tcp -m tcp --dport http -j DNAT --to-destination 192.168.23.33 /sbin/iptables -t filter -A FORWARD -d 192.168.23.30 -i ppp0 -o eth0 -p tcp -m tcp --dport ftp -j ACCEPT /sbin/iptables -t nat -A PREROUTING -d 1.2.3.4 -i ppp0 -p tcp -m tcp --dport ftp -j DNAT --to-destination 192.168.23.30 /sbin/iptables -t filter -A FORWARD -d 192.168.23.42 -i ppp0 -o eth0 -p tcp -m tcp --dport ssh -j ACCEPT /sbin/iptables -t filter -A FORWARD -d 192.168.23.42 -i ppp0 -o eth0 -p tcp -m tcp --dport smtp -j ACCEPT /sbin/iptables -t nat -A PREROUTING -d 1.2.3.4 -i ppp0 -p tcp -m tcp --dport ssh -j DNAT --to-destination 192.168.23.42 /sbin/iptables -t nat -A PREROUTING -d 1.2.3.4 -i ppp0 -p tcp -m tcp --dport smtp -j DNAT --to-destination 192.168.23.42 /sbin/iptables -t filter -A INPUT -p tcp -m tcp --dport ssh -j ACCEPT /sbin/iptables -t filter -A INPUT -p tcp -m tcp --dport http -j ACCEPT /sbin/iptables -t filter -A INPUT -p tcp -m tcp --dport ftp -j ACCEPT /sbin/iptables -t filter -A INPUT -p tcp -m tcp ! --syn --dport 1024:65535 -j ACCEPT /sbin/iptables -t filter -N MYCHAIN /sbin/iptables -t filter -A INPUT -s 192.168.23.42 -d google.com -p tcp -m tcp --dport 80 -j MYCHAIN /sbin/iptables -t filter -A INPUT -s 192.168.23.42 -p tcp -m tcp --dport 23 -j MYCHAIN /sbin/iptables -t filter -A INPUT -p tcp -j DROP /sbin/iptables -t filter -A MYCHAIN -j RETURN /sbin/ip6tables -t filter -P INPUT ACCEPT /sbin/ip6tables -t filter -F /sbin/ip6tables -t filter -X /sbin/ip6tables -t filter -A INPUT -p tcp -m tcp --dport ssh -j ACCEPT /sbin/ip6tables -t filter -A INPUT -p tcp -m tcp --dport http -j ACCEPT /sbin/ip6tables -t filter -A INPUT -p tcp -m tcp --dport ftp -j ACCEPT /sbin/ip6tables -t filter -A INPUT -p tcp -m tcp ! --syn --dport 1024:65535 -j ACCEPT /sbin/ip6tables -t filter -N MYCHAIN /sbin/ip6tables -t filter -A INPUT -s 192.168.23.42 -d google.com -p tcp -m tcp --dport 80 -j MYCHAIN /sbin/ip6tables -t filter -A INPUT -s 192.168.23.42 -p tcp -m tcp --dport 23 -j MYCHAIN /sbin/ip6tables -t filter -A INPUT -p tcp -j DROP
Repetitive, kinda cryptic, and it's difficult to spot the important parts. I don't know about you, but I'd prefer ferm
For more examples of ferm scripts, you might want to take a look at this or that.







Discussion
Hmmm,
I dunno, but you can write functions and have variables in a bash script too
. But then, I've never been in the need for more than a simple desktop firewall
.
The most important part here IMHO is the DSL, that it allows variables is a big bonus ;) The current ferm script on the server I mentioned currently has something around 400something lines, and you don't want to know to how many iptables rules that evaluates with both ipv4 and ipv6 in the mix