Monday, May 9, 2011

`iptables` for the Obsessive Compulsive

While I probably don't meet the clinical definition for being obsessive compulsive, I do tend to like to keep things highly organized. This is reflected heavily in the way I like to manage computer systems.
My employer, as part of the default security posture for production Linux systems, requires the use of iptables. If you've ever looked at an iptables file, they tend to be a spaghetti of arcana. Most tables start out fairly basic and might look something like:
-A INPUT -i lo -j ACCEPT
   -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
   -A INPUT -p udp -m udp --dport 53 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 587 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 993 -j ACCEPT
   -A INPUT -j REJECT --reject-with icmp-host-prohibited
This would probably be typical of a LAMP server that's also providing DNS and mail services.As it stands, it's fairly manageble and easy to follow if you've got even a slight familiarity with iptables or even firewalls in general.
Where iptables starts to become unfun is when you start to get fancy with it. I started going down this "unfun" path when I put in place a defense against SSHD brute-forcers. I had to add a group of rules just to handle what, above, was done with a single line. Initially, this started to "spaghettify" my iptables configuration. It ended up making the above look like:
-A INPUT -i lo -j ACCEPT
   -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
   -A INPUT -p udp -m udp --dport 53 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name ssh_safe --rsource
   -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 300 --hitcount 3 --name ssh_safe --rsource -j LOG --log-prefix "SSH CONN. REJECT: "
   -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 300 --hitcount 3 --name ssh_safe --rsource -j DROP
   -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 587 -j ACCEPT
   -A INPUT -p tcp -m tcp --dport 993 -j ACCEPT
   -A INPUT -j REJECT --reject-with icmp-host-prohibited
Not quite as straight-forward any more. Not "tidy", as I tend to refer to these kinds of things. It gave me that "tick" I get whenever I see something messy. So, how to fix it? Well, my first step was to use iptables' comments module. That allowed me to make the configuration a bit more self-documenting (if you ever look at my shell scripts or my "real" programming, they're littered with comments - makes it easier to go back and remember what the hell you did and why). However, it still "wasn't quite right". So, I decided, "I'll dump all of those SSH-related rules into a single rule group" and then reference that group from the main iptables policy:
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p udp -m udp --dport 53 -j ACCEPT
-A INPUT -p tcp -m comment --comment "Forward to SSH attack-handler" -m tcp --dport 22 -j ssh-defense
-A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 587 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 993 -j ACCEPT
-A ssh-defense -p tcp -m comment --comment "SSH: track" -m tcp --dport 22 -m state --state NEW -m recent --set --name ssh_safe --rsource
-A ssh-defense -p tcp -m comment --comment "SSH: attack-log" -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 300 --hitcount 3 --name ssh_safe --rsource -j LOG --log-prefix "SSH CONN. REJECT: "
-A ssh-defense -p tcp -m comment --comment "SSH: attack-block" -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 300 --hitcount 3 --name ssh_safe --rsource -j DROP
-A ssh-defense -p tcp -m comment --comment "SSH: accept" -m tcp --dport 22 -j ACCEPT
Ok, so the above doesn't really look any less spaghetti-like. That's ok. This isn't exactly where we-despaghettify things. The above is mostly meant to be machine read. If you want to see the difference in things, use the `iptables -L` command. Or, to really see the difference, issue `iptables -L INPUT ; iptables -L ssh-defense`:
Chain INPUT (policy ACCEPT)
   target     prot opt source               destination
   ACCEPT     all  --  anywhere             anywhere
   DROP       all  --  anywhere             loopback/8
   ACCEPT     all  --  anywhere             anywhere            state RELATED,ESTABLISHED
   ACCEPT     udp  --  anywhere             anywhere            udp dpt:domain
   ssh-defense  tcp  --  anywhere             anywhere            /* Forward to SSH attack-handler */ tcp dpt:ssh
   ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:domain
   ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:http
   ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:https
   ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:smtp
   ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:submission
   ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:imaps

   Chain ssh-defense (1 references)
   target     prot opt source               destination
              tcp  --  anywhere             anywhere            /* SSH: track */ tcp dpt:ssh state NEW recent: SET name: ssh_safe side: source
   LOG        tcp  --  anywhere             anywhere            /* SSH: attack-log */ tcp dpt:ssh state NEW recent: UPDATE seconds: 300 hit_count: 3 name: ssh_safe side: source LOG level warning prefix `SSH CONN. REJECT: '
   DROP       tcp  --  anywhere             anywhere            /* SSH: attack-block */ tcp dpt:ssh state NEW recent: UPDATE seconds: 300 hit_count: 3 name: ssh_safe side: source
   ACCEPT     tcp  --  anywhere             anywhere            /* SSH: accept */ tcp dpt:ssh
Even if you don't find the above any more self-documenting or easier to handle (in a wide xterm, it looks much better), it does have one other value: it makes it harder for people to muck up whatever flow or readability that your iptables configuration has. Because you've externalized a group of directives, someone's going to have to go out of their way to intersperse random rules into your iptables configuration. If it's just your own server, this probably has little value (unless you've got MPD). However, if you have shared administration duties, it can be a sanity-saver.

No comments:

Post a Comment