Making firewalling less of a headache

Great Wall of China. Picture taken by Ahazan and released into the public domain 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.

1) which had turned into a hand-knitted nightmare
2) “for Easy Rule Making”

Discussion

Michael KlierMichael Klier, 2008/09/02 15:36

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 ;-).

fooselfoosel, 2008/09/02 15:46

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 ;-)

Enter your comment (wiki syntax is allowed):
VHLJC
blog/2008/08/making_firewalling_less_of_a_headache.txt · Last modified: 2008/08/31 23:29 by foosel