iptables firewall templates

I use iptables firewalls on every server I administer, including all of our core routers (which run Linux too). There are lots of tools to easily configure a firewall. For simple tasks, Ubuntu now installs ufw by default, which has both command-line and GUI tools. For servers, consider Webmin.

If you want to do something more complicated, or prefer editing iptables rules yourself, you’ll have to do it by hand. When I first started doing this I found a template online and edited it to suit my need. Over time I’ve learned a lot more about iptables, and my templates have evolved.

Here is a simple basic firewall. The outline is taken from the output of iptables-save, and then hand-edited. I’ve left comments in to explain what each section does.

*filter
:FORWARD ACCEPT [0:0]
:INPUT DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Accept all loopback traffic
-A INPUT -i lo -j ACCEPT
# Drop invalid/unknown/spoofed TCP sessions
-A INPUT -p tcp ! --syn -m state --state NEW -j DROP
# Accept new sessions
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Accept important ICMP types
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 4 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 12 -j ACCEPT
# Accept known services
-A INPUT -p tcp -m tcp -m multiport --dports 22,25,80,443,465,587,993,995 -j ACCEPT
-A INPUT -p udp -m udp -m multiport --dports 53,5353 -j ACCEPT
COMMIT

Edit the last two lines for your specific services, and then save the file as /etc/iptables.up.rules and restore it:

iptables-restore < /etc/iptables.up.rules

The above is a default-deny firewall which allows all outgoing and loopback traffic, but filters incoming connections. This means that anything not specifically allowed will be dropped without notice.

Suppose you want to do this at your router to protect all your servers. Assuming:

  1. The router is the default gateway for both servers
  2. server1 at 192.168.0.100 is a mail server offering SMTP, POP, POP-SSL, IMAP, and IMAP-SSL
  3. server2 at 192.168.0.101 is a web server offering HTTP, HTTPS, and DNS
  4. all servers offer SSH, including the router

For routed (forwarded) traffic, you must use the FORWARD chain. In addition to ACCEPT and DROP, chains can call other named chains. I recommend creating a chain for each server you want to protect.

*filter
:FORWARD DROP [0:0]
:INPUT DROP [0:0]
:OUTPUT ACCEPT [0:0]
:server1 - [0:0]
:server2 - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp ! --syn -m state --state NEW -j DROP
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 4 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 12 -j ACCEPT
# if just a single port, you can avoid the multiport match extension and use dport
-A INPUT -p tcp -m tcp --dport 22  -j ACCEPT
# put common rules first
-A FORWARD -p tcp ! --syn -m state --state NEW -j DROP
-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A FORWARD -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A FORWARD -p icmp -m icmp --icmp-type 4 -j ACCEPT
-A FORWARD -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A FORWARD -p icmp -m icmp --icmp-type 11 -j ACCEPT
-A FORWARD -p icmp -m icmp --icmp-type 12 -j ACCEPT
# all servers offer SSH
-A FORWARD -p tcp -m tcp --dport 22  -j ACCEPT
# send specific traffic to custom rules
-A FORWARD -d 192.168.0.100 -j server1
-A FORWARD -d 192.168.0.101 -j server2
-A server1 -p tcp -m tcp -m multiport --dports 25,110,143,993,995 -j ACCEPT
-A server2 -p tcp -m tcp -m multiport --dports 80,443 -j ACCEPT
-A server2 -p udp -m udp --dport 53 -j ACCEPT
COMMIT

It's possible to reduce complexity by putting the common rules in another chain and calling it from both INPUT and FORWARD:

*filter
:FORWARD DROP [0:0]
:INPUT DROP [0:0]
:OUTPUT ACCEPT [0:0]
:common_fw - [0:0]
:server1 - [0:0]
:server2 - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -j common_fw
-A FORWARD -j common_fw
-A FORWARD -d 192.168.0.100 -j server1
-A FORWARD -d 192.168.0.101 -j server2
-A common_fw -p tcp ! --syn -m state --state NEW -j DROP
-A common_fw -m state --state ESTABLISHED,RELATED -j ACCEPT
-A common_fw -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A common_fw -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A common_fw -p icmp -m icmp --icmp-type 4 -j ACCEPT
-A common_fw -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A common_fw -p icmp -m icmp --icmp-type 11 -j ACCEPT
-A common_fw -p icmp -m icmp --icmp-type 12 -j ACCEPT
-A common_fw -p tcp -m tcp --dport 22  -j ACCEPT
-A server1 -p tcp -m tcp -m multiport --dports 25,110,143,993,995 -j ACCEPT
-A server2 -p tcp -m tcp -m multiport --dports 80,443 -j ACCEPT
-A server2 -p udp -m udp --dport 53 -j ACCEPT
COMMIT

Where to save and how to load your rules depends on the distribution. Ubuntu and Debian don't have a standard way to load an iptables ruleset on boot. So I borrowed Webmin's practice of putting a line in /etc/network/interfaces:

# The primary network interface
auto eth0
iface eth0 inet static
        address 192.168.0.2
        netmask 255.255.255.0
        gateway 192.168.0.1
        post-up iptables-restore < /etc/iptables.up.rules

Users of RPM-based systems like Fedora, RHEL, and CentOS have it easier. Save your rules in /etc/sysconfig/iptables, and they'll be loaded at boot or whenever you restart networking. However, Red Hat systems come with a simple firewall manager named lokkit, which creates its own unusual chains like "RH-Firewall-1-INPUT". Here is an example from a CentOS 4 server I administer:

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:RH-Firewall-1-INPUT - [0:0]
-A INPUT -j RH-Firewall-1-INPUT
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited

You should never expect your hand-edited files to work with a firewall management tool like ufw or lokkit. However, if you are careful to obey the expected format these tools use, you can make small edits by hand while still using the tool. For instance, all of my templates are compatible with Webmin's firewall editor.

Tags: , , ,

  1. odi’s avatar

    I suggest the FORWARD table should not do state based filtering. The state is kept on either end of the connection and trying to follow that state in a router in between is futile. Just think what happens on packet loss. Duplicate ACKs or retransmitted packets MUST NOT be dropped by a router, as it can not know if the original packet has reached the destination. The FORWARD table should merely perform access restrictions and address validation. Filtering is the job of the destination.

    Reply

    1. Tyler Wagner’s avatar

      Wrong, and wrong. Filtering is precisely the job of a stateful firewall between the host and attackers. And duplicate ACKs or retransmitted packets will not be dropped by the above firewall – they are recognized by the ESTABLISHED,RELATED rule.

      Reply

Reply to Tyler Wagner Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.