iptables Gilder

Connectivity 101
IOS Basics

Can I firewall my Slice?

Definitely and we encourage it. Every Slice can run iptables.

Thats what Slicehost says on their FAQ page. As a network engineer trained on Cisco firewalls I find that Cisco access lists are a good analogy to iptables. For those of you know don't know iptables is a Linux software firewall, and unlike windows firewall, it doesn't suck.

Now before I go on I must give a bit of a warning. Iptables controls how your slice communicates with the outside world at the kernel level. Typos and other mistakes can block legitimate traffic, cause strange behavior, or lock you out of your slice completly. I recomend that you play with this on a 256 meg slice that you don't care about for a while before you try it on a production system. I'll say it again in big letters in case you missed it:


If you break your slice, congratulations, I hope you leared something. (I broke mine 3 times, it was a great learning experience.) Just don't come crying to me if you bring down a production system. Also, all iptables commands require root privlages so su and sudo are going to be your friends. (Your not ssh'ing into your slice as root are you? *cringe*)

The first thing to do is to make sure that your slice has iptabels installed. The easiest way to check this is with the command "iptables -S". (Note that all iptables commands are completly case sensitive.) This shows you your current IP tables rules. On a fresh slice it should look something like this:

root@hostname:/root# iptables -S

This means that the default policy for packets that don't match a rules to allow them to pass. Since we are trying to firewall our slice the one we are interested in is "INPUT". (FORWARD is used in routing and OUTPUT controls outbound traffic.) If its not installed then you'll need to use your distribution's package managment system to install it, or compile and install from source.

Now the best way to configure a firewall is to block everything that isn't explicitly allowed by a rule. So how do we do that if the default policy is to allow everything? Easy, we put in a deny rule, or "DROP" as ipables calls it. (Its best to leave the default policy at ACCEPT. That way if you mess up you can flush the rules from console with "iptables -F" and get back into your slice.) But before we can drop everything that isn't allowed we need to define what is allowed.

When configuring a firewall there are essentially 6 catagories of traffic:
  1. Traffic that is part of an existing TCP or UDP session.
  2. Traffic that we always want to allow for connectivity testing.
  3. Traffic from IPs that we trust completly
  4. Traffic from IPs that we trust only so much.
  5. Traffic from IPs that we don't trust at all.
  6. Traffic from everyone else.
The first catagory of traffic is always allowed through the firewall. This is what makes a firewall stateful, its ability to look at the packets passing through it and match them to known active TCP or UDP sessions. (ok there are more IP based protocols than those two but I am not covering them here) This way it knows what packets to actually filter. (no point in filtering a packet if the session has already been allowed) Blocking this traffic can result in very strange behavior. And unlike a Cisco firewall, iptables does not allow this traffic through by default once you start blocking anything that isn't explicitly allowed.

The second catagory basically covers the different types of ICMP traffic. Ping and traceroute are great for diagnosing connectivity issues on a network and makeing sure that your slice hasn't exploded. Typically echo, echo-reply, traceroute, and destination-unreachable messages are allowed through for troubleshooting purposes. Other types of ICMP traffic are typically blocked as good security practice. Timestamp requests for example are typlically blocked for PCI compliance reasons. There is an excelent listing of the ICMP types here.

The third catagory is generally allowed through the firewall on all ports. This is typically traffic from IPs that we know are ok. Such as the static IP at your office or your house. Or the IP range that your application development team comes from.

The fourth catagory is generally granted access through the firewall that is more than what the general public has but is still limited only to certain things. Good examples would be the IP of an outsourced web development team that needs access to FTP but not SSH. Or offices that need to be able to access POP3 and HTTPS for mail and a secure web application but not the MySQL database.

The fifth catagory of traffic is generally blocked completely. This typically encompasses IPs that we know are out to get us. Examples for this catagory would the bot that makes spam posts on your fourms or the guy who's always trying to find buffer overflows in your PHP code. Now this won't help against a bot net but its good for the annoying script kiddie who is dumb enough to use his own broad band connection.

The sixth catagory covers traffic from the rest of the world. Since we can't really list IPs here this is typically defined as ports that we want the entire world to be able to connect to. This generally covers what your slice actually does, ie, the reason you bought it. Good examples here are web server (port 80-tcp and maybe 443-tcp), mail server (port 25-tcp and maybe ports 110-tcp and/or 143-tcp), and DNS server (port 53-tcp/udp).

To make it easier these catagories have names. Stateful, ICMP rules, administrative IPs, restricted IPs, banned IPs, and global services or ports. So how do you make iptables rules for these things? Well the stateful rules look like this:

iptables -I INPUT 1 -i eth0 -p tcp -m state --state ESTABLISHED -j ACCEPT
iptables -I INPUT 2 -i eth0 -p udp -m state --state ESTABLISHED -j ACCEPT

So what does that mean? Well the first part is the name of the command, iptables of course.

The next flag, -I, says that we are inserting a rule. (-D deletes and -R replaces)

The next part says what chain we are modifying, INPUT, the one that filters traffic comming in from the outside world.

The number indicates what line we want the new rule to be on. Just like Cisco ACLs the rules are processed in order from top to bottom and the parsing stops on the first rule that matches the packet in question. (think of it as a case switch statment in C++) So order matters. If you do not enter a number it defaults to 1. If there is already a rule on the line you select (and you used -I to insert) then that line and all below it are moved down by one and the new rule is placed where you specified. Useful for inserting new rules into an existing ruleset.

The next flag, -i, says that we want to specify which interface the rule should apply to. In this case eth0 is what slices use for talking to the outside world. (eth0:X sub interfaces are included) If you omit this flag the rule will apply to all interfaces on the machine. (including tap and tun interfaces)

The -p flag says what protocol the rule should apply to. (tcp, udp, icmp, or something from /etc/protocols) It defualts to "all" if the flag is omitted.

The -m flag specifies which inspection module to load, this is typically the same as the protocol declared with the -p flag but stateful rules are a special case and use the state module.

The next flag uses two dashes because its actually an argument for the module we just loaded and not the iptables command itself. For the icmp module it specifies ICMP types, for the tcp and udp modules it specifies port numbers, and for the state module it lets us select established connections for a statful firewall setup.

The final flag, -j, stands for jump. It tells iptables that the next argument is what to do with a packet that matches the rule. The basic possible values are REJECT (port closed), DROP (port stealthed), and ACCEPT (port open). There are more but they are outside the scope of this guide.

So to allow icmp echo, echo-reply, unreachable, and traceroute (icmp type numbers 8, 0, 3, and 30 respectivly) and add the rules below our stateful inspection rules on lines 1 and 2 you would do this:

iptables -I INPUT 3 -i eth0 -p icmp -m icmp --icmp-type 8 -j ACCEPT
iptables -I INPUT 4 -i eth0 -p icmp -m icmp --icmp-type 0 -j ACCEPT
iptables -I INPUT 5 -i eth0 -p icmp -m icmp --icmp-type 3 -j ACCEPT
iptables -I INPUT 6 -i eth0 -p icmp -m icmp --icmp-type 30 -j ACCEPT

For administrative IPs the commands are much simpler. Since we are giving them access to all ports we don't need to specify a protocol or port number. We just need to specify the interface and then use the -s flag to specify the source IP to let in:

iptables -I INPUT 7 -i eth0 -s -j ACCEPT
iptables -I INPUT 8 -i eth0 -s -j ACCEPT

Notice the /32 after each IP. This is CIDR notation for a single host. If you want to specify a range of IPs just use the network ID and the appropriate CIDR netmask. If you don't know subnetting, its something you will need to lean if you are serious about networking. For example:

iptables -I INPUT 9 -i eth0 -s -j ACCEPT

To grant an external IP access to only a certain port we simply combine the -s flag with the -p flag to specify a source address and a protocol. We then add the --dport flag to specify the destination port that we want that IP to be able to access. These examples give single IPs access to FTP (port 21) and MySQL (port 3306) and opens POP3 (port 110) for a /24 CIDR range:

iptables -I INPUT 10 -i eth0 -p tcp -m tcp -s --dport 21 -j ACCEPT
iptables -I INPUT 11 -i eth0 -p tcp -m tcp -s --dport 3306 -j ACCEPT
iptables -I INPUT 12 -i eth0 -p tcp -m tcp -s --dport 110 -j ACCEPT

Up next is the banned IP catagory. Rules here are pretty short since just like admin IPs we are only interested in the source IP address. Only the action after the -j flag is different:

iptables -I INPUT 13 -i eth0 -s -j DROP

Now we define our globally open services. (Make sure that your banned IPs are listed before your global service rules, remember order matters.) Lets say we are runing a web site with a secure shopping cart and a SMTP mail server for sending emails to customers. So thats ports 80, 443, and 25.

iptables -I INPUT 14 -i eth0 -p tcp -m tcp --dport 80 -j ACCEPT
iptables -I INPUT 15 -i eth0 -p tcp -m tcp --dport 443 -j ACCEPT
iptables -I INPUT 16 -i eth0 -p tcp -m tcp --dport 25 -j ACCEPT

And finally we want to drop anything that didn't match one of our permit rules. This is probably the easiest command and the most dangerous. Make sure that the permit rules you put in place still allow you access to your slice via SSH or whatever remote managment method you use. If you lock yourself out of your slice you can issue the "iptables -F" command as root from console to flush the iptables rules and fall back to the default "allow everything" policy to start over. Here we go:

iptables -I INPUT 17 -i eth0 -j DROP

Just matches the interface and drops everything. Simple. Congradualtions you have taken a large step towards securing your slice. If you look over the example rules only 18 external IPs have access to SSH on port 22 and only 19 have access to the database. Now Billy Scriptkiddie can't even try to brute force your SSH login or inject junk into the MySQL database. You almost don't need good passwords. Ok, well, you still need good passwords of course. But you get the idea.

Another fun flag is -d for destination IP. Useful if you have more than one public IP on your slice and want to grant someone access only to that IP and not the rest of them. Like so:

iptables -I INPUT 12 -i eth0 -p tcp -m tcp -s -d --dport 21 -j ACCEPT

Another neat trick is to put a ! in front of almost any argument to inverse it. So "-i ! eth0" for all interfaces but eth0 or "-p ! udp" for all protocols but UDP or "-s !" for all source IPs except Get creative.

But wait! You run a passive FTP server and opening port 21 lets them log in but they can't do anything!

Well, FTP is an odd (old) protocol. It actually runs over two ports. One for commands, port 21, and another for data. The port used for the data connection depends on the type of FTP being used. Active FTP uses port 20 for data so you can just make an iptables rule for that. Passive FTP uses a random high number port for the data connection so getting that through firewalls that don't have a specific function for handling passive FTP can be tricky. The two simple solutions are to use active FTP and open port 20 or to make the client's IP administrative for passive access. There is a more complex but more elegant solution that I have not personally tried here.

Personally I say just ditch FTP and go with SFTP. It runs on a single port (whatever you set SSH to) and provides excellent encryption for everything you send. Your slice already supports it anyway and SFTP clients are everywhere.
Valid HTML 4.01 Transitional