Sunday, 2. May 2010
Introduction
You can increase security in your network by implementing a stateful firewall in iptables. A stateful firewall can track the state of connections so that established connections can be differentiated from new connections. This facility allows iptables rules to deny connections on all ports by default, and allow traffic on higher ports only if that traffic originated on one of the ports that are explicitly opened.
Such a firewall protects from connection hijacking (as the traffic from the hijacker’s IP would be categorized as NEW and dropped), and since ports can be closed by default, the system administrator can turn on any services they desire, secure in their knowledge that the outside world can only access the services they specifically allow.
Preparation
There’s just a few things we need to do before we’re ready to go.
Assumptions
This configuration should work as-is for almost everybody, but I must assume that you:
- Are configuring
iptables manually. I am not familiar with Shorewall or other iptables configuration utilities; but I think you’ll find iptables straightforward and extra utilities an unnecessary complication, if you’re familiar with IP networking.
- Have only one network interface on an untrusted, external connection. I call this your external interface,
$EXTIF. Typically this would be the interface with a public IP address.
If all of your IP addresses are RFC 1918 private addresses, this would be the interface that corresponds to the default route (in this case you are almost guaranteed to be firewalled anyway, since your private addressesmust go through a Network Adress Translating router, but you can still follow the direction here to firewall this connection if it’s untrusted).
If you have more than one untrusted connection, you’ll have to duplicate the rules for the trusted connection (since negating $EXTIF won’t define an exclusive list of trusted networks), but, since you’ve already become familiar with the process of configuring multiple public connections, you won’t have a problem with this modification.
- Trust all traffic on all interfaces other than
$EXTIF. This allows us to make one rule for all such trusted connections. If this isn’t true, you’ll have to duplicate some of the rules for each trusted network because they cannot be defined as ‘all interfaces that do not match $EXTIF, but this is not difficult.
- Use bash. A few of the commands won’t work on other shells – you’ll have to write individual rules to take the place of the bash
for loops, but this is trivial.
Before you Begin
To get the most out of this howto, you’ll need to know
- The external interface you’ll be firewalling
- The ports you’d like to leave open to the world
- The addresses of servers behind the firewall, to which you’d like to forward ports (if any)
- Whether you’d like to enable IP Masquerading so that other computers can connect to the internet through your firewall.
Kernel Configuration
Your kernel must be configured to support ipTables, connection tracking, and masquerading.
[*] Networking support --->
Networking options --->
[*] Network packet filtering framework (Netfilter) --->
Core Netfilter Configuration --->
< M > Netfilter connection tracking support
< M > FTP protocol support [ if masquerading ]
[ Enable other protocols you use, if masquerading ]
{M} Netfilter Xtables support (required for ip_tables)
< M > "state" match support
< M > IPv4 connection tracking support (... [ if masquerading ]
< M > IP tables support (required for filtering/masq/NAT)
< M > Packet filtering
< M > REJECT target support
< M > Full NAT [ if masquerading ]
< M > MASQUERADE target support
< M > REDIRECT target support
As you can see, some of those are only required for masquerading; if there are computers behind your firewall that will connect to the Internet or provide services through your firewall, you’ll need to enable at least most of the [ masquerading ] options. The configuration shown here uses modules, so no additional memory
will be wasted if you configure options you aren’t using.
IPTables Starting Point
We will flush all tables and set their default policies to ACCEPT. We do this so we have a fresh starting point.
iptables -P INPUT ACCEPT
iptables -F INPUT
iptables -P FORWARD ACCEPT
iptables -F FORWARD
iptables -P OUTPUT ACCEPT
iptables -F OUTPUT
# for masquerading/DNAT
iptables -t nat -P PREROUTING ACCEPT
iptables -t nat -F PREROUTING
iptables -t nat -P POSTROUTING ACCEPT
iptables -t nat -F POSTOUTING
iptables -t nat -P OUTPUT ACCEPT
iptables -t nat -F OUTPUT
Now any traffic will be accepted and we’ve cleared out all iptables rules. We’re ready to start the configuration.
Configuring iptables
Defining Your Settings
Here’s where you’ll be using your knowledge of your system(s) to personalize your configuration. To accomplish this, I’ll provide a few environment variables whose values you should alter to fit your configuration.
# set this to whatever interface is external (see above).
EXTIF="eth0"
# set this to the list of ports you'd like to accept traffic on.
# this default list allows SSH and HTTP. You might want to add others
# to this list.
PORTS="22 80"
# an example that also opens HTTPS, DNS, IMAP/IMAPS
# PORTS="22 80 443 53 443 143 993"
Execute these lines in your shell to set environment variables specific to your configuration; then you can copy and paste the lines below verbatim.
INPUT
The input table is used for any incoming IP traffic, including traffic that will eventually be forwarded through to other servers behind the firewall. The majority of settings will be put in this table, as it’s the first line of defense in disallowing undesirable traffic.
Allow all traffic on any but the external interface
We accept any traffic that doesn’t come in on the external interface, opening all ports by default on all interfaces that don’t match $EXTIF.
iptables -A INPUT -i ! $EXTIF -j ACCEPT
Allow Acceptable Traffic
First we allow traffic destined for the ports we’d like to open.
for p in $PORTS; do iptables -A INPUT -p tcp --dport $p -j ACCEPT; done;
Established connections between public hosts and computers behind the firewall will use high level ports to communicate. Some services might also move to a high level port to allow the port associated with the service to be kept open. In both these cases, we’d like to allow incoming traffic only if it is related to an established connection. This is the iptables rule that makes this configuration stateful.
iptables -A INPUT -i $EXTIF -m state --state ESTABLISHED,RELATED -j ACCEPT
Finally, you may allow ICMP traffic if you like. Dropping all ICMP traffic is the safest option, but many servers (including Google’s) reply to ICMP traffic, at least to PINGs. To do the same, execute the following command.
iptables -A INPUT -p icmp -j ACCEPT
Drop all other traffic
We now set a default policy to DROP all traffic that doesn’t match the above rules. This disallows traffic going to ports we didn’t explicitly open as well as traffic that speaks to a port that was opened for another connection (eg a hijack attempt).
If you are logged in via SSH, you would be wise to ensure that you haven’t forgotten to allow your own connection. I generally run the following command first. If my ssh session doesn’t lock up for 10 seconds, I can enable the policy permanently. If you’re at a local console, and not logged in over the network, this failsafe does nothing for you.
iptables -P INPUT DROP; sleep 10; iptables -P INPUT ACCEPT;
If your terminal doesn’t lock up for 10 seconds, it means that your connection wasn’t interrupted by the DROP policy and you can enable it permanently.
iptables -P INPUT DROP;
OUTPUT and FORWARD tables
At this point we’re already dropping incoming connections we don’t want. This generally ensures that any traffic that gets through the INPUT is meant to be allowed. Therefore, you may leave the FORWARD and OUTPUT tables empty with a default policy of ACCEPT, if you’d like to.
However, you might want to make extra sure that you won’t forward anything through to internal networks. This is an appropriate configuration if you have a number of servers on external IPs, that also use a private, internal network on the back end for high speed communications between servers. In this case you might want to make sure that you aren’t functioning as a router for these other servers; you don’t want to FORWARD packets through to internal networks.
# drop packets from the public network to internal networks.
# do NOT do this if you want to configure a router / gateway!
iptables -A FORWARD -i $EXTIF -o ! $EXTIF -j DROP
You could use the OUTPUT table to disallow packets based on the port to which they’re being directed. This might be desirable in some situations, but typically you’ll just want to leave OUTPUT empty with a default policy of ACCEPT.
NAT Tables
If you’d like to redirect packets to other servers behind the firewall (DNAT), or allow internal computers to connect the the Internet through your firewall (Masquerading), you’ll also want some rules in your NAT table. (if neither applies to you, you may leave all the NAT tables empty with a default policy of ACCEPT).
Allow Computers to Connect to the Internet through this Firewall
If you wish internal computers to connect to the Internet through this firewall, you may enable IP Masquerading. In this case, all traffic originating on the internal network will be altered by the firewall to appear to originate from the firewall itself. When response traffic is returned, the firewall performs reverse alterations and forwards the packet to the real origin. In this way, the firewall performs NAT, making all public computers unaware that they are communicating with a system behind the firewall, rather than the firewall itself.
# masquerade as the origin of all traffic leaving on the external interface
iptables -t nat -I POSTROUTING -o $EXTIF -j MASQUERADE
Clone One Open Port on Another
In some situations, you may want to redirect traffic coming in on port A to port B. Consider an example; you run a mail server on port 25, but sometimes have to submit mail to the server from systems behind firewalls where port 25 connections are blocked. Blocking port 25 outgoing is common for public networks – internet cafes and the like – as well as many residential ISPs. In this case, you might want to open a diffrerent port – say, 26 – that you’d like to be redirected to port 25 so you can submit mail on either 25 or 26. You can generally configure a mail server to listen on more than one port, but an iptables rule is easier.
Case I: You’d like to redirect traffic to another port on the firewall itself
In this case you’d like to redirect the traffic to the firewall. Hint: this is also what you want if you’d like to force outgoing traffic through a proxy, but you’ll have to modify the rule!
# we modify the traffic pre-routing, effectively routing to ourselves instead
iptables -t nat -I PREROUTING -p tcp --dport 26 -j REDIRECT --to-ports 25
Case II: You would like to pass incoming connections through to servers behind the firewall
Perhaps your servers are behind the firewall, but you’d still like them to server public connections. To do so, you’ll have to add a slightly different rule. For the purposes of our example, we’ll redirect traffic on port 221 to an internal host, 192.168.125.22 port 22, enabling the admins to log into the machine over SSH from the outside world. You might find this convenient for many of your firewalled hosts!
iptables -t nat -I PREROUTING -i $EXTIF -p tcp --dport 221 -j DNAT --to-destination 192.168.125.22:22
Traffic coming in on $EXTIF on tcp port 221 to 192.168.125.22:22.
Hint:, if the incoming port (221, here) and the real destination port (22, here) are the same, you need only specify the IP address as a value to --to-destination. The default destination port is the same port as specified for --dport.
Allow Forwarding
To make use of this rule, your firewall must be allowed to forward IP traffic between interfaces. This is typically disabled by default since it can expose networks that were meant to be isolated. You may enable it with sysctl:
sysctl net.ipv4.ip_forward=1
or with a simple echo, through /proc:
echo 1 > /proc/sys/net/ipv4/ip_forward
In either case, you’ll probably want to save this configuration in /etc/sysctl.conf so that it is re-enabled each time the system boots.
# the following line could be added to /etc/sysctl.conf
net.ipv4.ip_forward = 1
Wrapping Up
Your firewall is now configured. However, you’ll want to ensure that the iptables rules you’ve just configured will persist between reboots. Each distribution has a different way of doing this; if you like, you can simply put your commands in a local runscript. In Gentoo, my distro of choice, you would execute the following.
/etc/init.d/iptables save
Gentoo’s init system will restore your rules when it reboots, as long as you make sure the iptables initscript is in your boot or default runlevel.
On other distributions, you’ll have to find your own way of accomplishing this.
Conclusion
Now you’ve set up your very own stateful firewall. Your traffic will be sanitized and you can start services with reckless abandon, knowing that you’ll have to open ports before those services are externally accessible. And now that you’ve been introduced to iptables, hopefully you’ll be able to take advantage of the many excellent applications of this excellent firewall solution.