Monday, April 04, 2011

Introduction

Setting up a firewall on your *nix box, being it a workstation, laptop, or server, is always a good idea. In most cases, you can do with some simple firewall rules, f.e. on your laptop, block all incoming requests (except the established connections, i.e. the replies on the outgoing requests you made), or on a simple webserver (allow port 80 only).

But if you need more complex rules, f.e. a server that hosts a website available for the entire internet, but with an ssh and samba service that should only be available for the local subnet, or even some specific IP addresses, it becomes a bit more complex.
And if you want to filter the outgoing traffic as well, your iptables rules get a mess after a while, and when you want to change anything, chances of a mistake or forgetting something are high, which may result in locking yourself out of your box (at least for remote access), or leaving something open that shouldn't.

To make your rules more manageable, you can make use of chains in your iptables rules. I got some inspiration in an article that uses chains to make iptables more efficient (faster). My goal was to get easier to read and configure iptables rules, but it will result in faster handling of packets as well.

Setup

A web service should be available from all networks (i.e. internet) on port 80 (http) and 443 (https)

The server can be managed remotely using ssh (port 20) and webmin (port 10000), but only from a limited set of IP addresses (admin PC's).

The server hosts a samba service (several TCP and UDP ports), that should only be available from a limited set of IP addresses (admin + webmaster PC's).

Outgoing connections will be filtered, but some services should be allowed (dns, dhcp, smtp, ntp) and some external websites should be available to get updates.

Concepts

ESTABLISHED state

When using this option, you can filter for established connections. If you define it in both the INPUT and OUTPUT rules, you only have to define in the INPUT rules which NEW incoming requests should be allowed, and in the OUTPUT rules which NEW outgoing request are allowed. The established connections will be allowed and should not be redefined (making the configuration a lot more readable and maintainable). An example allowing only an ssh service without using the ESTABLISHED state would be :

Basically, every incoming/outgoing connection is dropped, except if the incoming packet has port 22 (ssh) as destination, or if the outgoing packet was sent from port 22 (which is the reply of the ssh server).

Now, every incoming/outgoing connection is dropped, except if the incoming packet has port 22 (ssh) as destination, or if the packet belongs to an established connection. Because incoming connections to port 22 are allowed, the firewall will remember a packet coming in, creating a 'connection' for the host/port the packet originates from when the ssh server replies to it. So when the reply of the ssh server is sent out, it matches an 'established' connection and will be allowed out.

In this example, the benefit of using the connection state is not clear, but when more allowed incoming services are added, they only have to be added on the INPUT chain, but not on the OUTPUT chain, because they are covered by the ESTABLISHED rule.
In the first example (without the ESTABLISHED rule), every allowed incoming connection should be repeated in the OUTPUT chain, matching the packets sent for the outgoing connection, which results in an equal amount of rules on both chains.
If you want to do filtering in both directions (allowing incoming request for listening services and outgoing request for remote services), this can become very messy, and almost unmaintainable without making mistakes.

Introducing chains

When two services (on different ports) should be available to a limited but identical list of IP addresses.
Without using chains, for every combination of port and IP a rule should be created :

A list of allowed external websites for updates (mirrors of Debian, webmin and Drupal, in this example).
All other requests for external websites are logged. This can be useful for monitoring : notification of abuse, or if you forgot to add an allowed website.

This is of course, just an example. There are many variations possible. Maybe you want to add an ftp server, that is only accessible from a specific subnet and some other IP addresses. Or you can allow outgoing ssh-connections to a pool of servers. If you want to protect your server from scanning techniques (XMAS, NULL, ...) you can create a seperate chain for that as well. Options are limitless and depending on your needs.

But the idea is that the firewall rules are now much easier to understand and change. For example, if you want to make ssh (port 22) available also for webmasters, or for the entire internet, just change the -j option to webmaster_IP or ACCEPT. Adding an IP address for an admin PC, is just adding one line to the admin_IP chain.

BTW: I didn't go into every detail of iptables options that are used in my examples, so if you like more information on all available options of iptables and on how iptables works, you can take a look at this tutorial.

9 comments:

How would one do in the case where I have a server with two network interfaces, one external facing internet and one internal (openvpn) interface?For example; I want to access this server only from a specific subnet arriving only on the vpn tun-inteface.Would it make any differance or would you do exactly the same? I mean, there is not a very big chance that 172.20.42.0/24 will access the server from external interface anyway.../Stefan

Stefan:With the -i, or --in-interface option, you can add a filter based on the network interface on which a connection is made.If you want to limit ssh access from only the openvpn interface, you can modify the rule from the example (see blogpost), by adding the -i option :

iptables -A INPUT -p tcp -m tcp --dport 22 -i openvpn -j admin_IP

('openvpn' is the interface name, use ifconfig to get the exact name of this interface)

or, if you want to allow ssh on all adapters but the main interface, use this :

iptables -A INPUT -p tcp -m tcp --dport 22 -i !eth0 -j admin_IP

(the '!' before the interface name inverts the filter, allowing anything but eth0)

Good point. It is a rule I took over from the example I based my rules on, years ago. Googling for it shows a lot of other examples with the same rule.

That said, the required ACK (either just the ACK to establish or close the actual connection (3rd step in the 3-way handshake), or a part of any sent package in an established connection) is covered by the 'established' state check, or the rules that allow incoming packets to some ports.

So, both establishing, closing and sending packets on an established connection should still be left through with the current rules (and on the allowed ports)

So I can't think of a reason why it is absolutely necessary in this example, unless I'm missing something ;-), and the rule can probably can be left out.Thanks for noticing.

Thanks a lot man.. iptables always used to intimidate me until i read your brilliant post.. can't thank you enough, I was really stuck with looking at rules examples and the stupid?! Ubuntu document. Thanks also for the the perfect iptables book link you posted at the end.. I can't thank that author as well. If I saw you in front of me now I'd hug u man :).. Don't worry, bro grab ;)

Forgot to ask: what is the default policy that you use for your chains?

I would guess and say 'ACCEPT' since it's safer in case you flushed the tables and I guess at the end, you already have a rule at the bottom that rejects all packets that don't comply with the rules.. Am I correct on this?

Yes, ACCEPT as default policy will work, but in these examples it doesn't really matter, because all chains end with a DROP rule, without parameters, so this behaves like the a default rule.If you would set default to DROP for a chain, you can leave out the last DROP rule in that chain, but I left it there to be more easy to understand.