Simple Firewall Configuration Using NetFilter/iptables | SUSE Communities

Simple Firewall Configuration Using NetFilter/iptables

Share
Share

Most major Linux distributions, SuSE ones included, feature some user interface for firewall configuration. There’s nothing wrong with them but I couldn’t get quite the configuration I wanted and chose to create configurations manually. The iptables man pages are really a documentation of syntactical detail of the iptables command line and don’t provide guidance on composition of a firewall from a series of rules. There’s a lot of scattered information about iptables that can be found using your favourite search engine but none of it quite taught me what I needed to know. In the end I figured out what I needed using a Vmware virtual machine running SuSE Linux Pro 10.0. The following is offered as documentation of simple firewall configuration using iptables. Verifying that the resultant firewall adequately secures the relevant hosts is left as an exercise for the reader.

Even though it’s hard to get to grips with, iptables/NetFilter is fabulously cool. The set of functionality is broad and the configuration method very expressive. That’s an advantage if the distribution included firewall features don’t quite give you what you need.

The SuSE firewall configuration and resultant scripts use multiple custom groups of rules. My goal is to show a simple firewall example that has a waterfall style in default groups only. I found it easier to understand the resultant firewall configuration script and also found it easier to express my intentions.

iptables/NetFilter Elements

NetFilter is the set of kernel components that actually executes the firewall rules. iptables is the program that is used to define and insert the rules. From this point forward I may use iptables to refer to NetFilter.

iptables configuration requires specification of a “table”, a “chain” and the rule details. A chain is a group of rules. The rules in a chain are applied in a context defined both by the chain itself and a “table”. The table is a group of chains. The table defines a global context and chains define a local context within that table. The simplest example would be to consider the host running the firewall. It can receive packets (input) and send packets (output). Assume we want to filter traffic going in and out of the host. The global context (table) is “filter”. The local contexts (chains) are “input” and “output”. A Linux host can also operate as a router and forward packets. So, the filter table also has a “forward” chain.

You are probably quite familiar with Network Address Translation (NAT). This is a method of multiplexing IP address space. iptables has a group of chains in a nat table. The prerouting and postrouting chains contain rules that act on packets as they arrive and leave the host respectively plus there is an output chain to act on packets from the host itself.

There are two other tables, mangle and raw, that I won’t discuss in detail here.

What Are We Trying To Build

For my own use, I wanted to use Linux and iptables to replace a Windows NAT router/firewall at home. I have a few hosts on the private network so I need a NAT router to give them Internet access. A couple of my private hosts run servers I want to expose to the Internet and there are some services running on the firewall itself that I want to expose to the Internet. It’s arranged something like this:

Network Host Description
192.168.1.0/24   Private network I want to be able to access Internet (via NAT routing)
  192.168.1.1 The private interface on the router
10.0.0.0/24   The public network the router is connected to
  10.0.0.1 The public interface on the router (the real router has a routable, static IP address). This should be locked down except for the services I want to be exposed
  192.168.1.254 A smtp/pop mail server that I want to be able to access from the Internet
  192.168.1.253 A http server that I want to be able to access from the Internet
  10.0.0.1:2202 A SSH server on the router but re-configured to use a non-standard port

Getting Started

The firewall is going to be configured using a [bash] shell script. The first thing I want to do is define some of the things I will use repeatedly:

# Private interface
IF_PRV=eth0
IP_PRV=192.168.1.1
NET_PRV=192.168.1.0/24

# Public interface 1
IF_PUB=eth1
IP_PUB=10.0.0.1
NET_PUB=10.0.0.0/24

# Others
ANYWHERE=0.0.0.0/0

This will let me use names for the various network elements and be able to change things easily in the future.

Each chain is used waterfall style. A packet is tested against each rule in turn and processed according to any matching rule. Each chain has a rule of last resort called the “policy”. We start then with fairly restrictive policies:

iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

This sets the policy for the three chains in the filter table to drop all packets. Note that the table isn’t specified. iptables defaults to the filter table when none is specified.

Next we want to remove any existing rules from the tables:

iptables -F -t nat
iptables -F -t mangle
iptables -F -t filter
iptables -X

The first three statements flush all the rules from the nat, mangle and filter tables respectively. The last statement removes all user-defined chains.

Routing

The firewall I’m building here is also going to be a translating router. The IP stack on Linux can act as a router and it can be enabled quite simply:

echo 1 > /proc/sys/net/ipv4/ip_forward

Forwarding Rules

Since we are going to use the host as a router we’ll start with the forwarding rules. We trust the private network so we want to allow all routed traffic from it:

iptables -A FORWARD -i $IF_PRV -o $IF_PUB -j ACCEPT

This accepts for forwarding, traffic inbound on the private interface and outbound on the public interface. This isn’t enough though. The filtering is performed on a per-packet basis. We need to allow the traffic back from the public interface to the private. We can’t allow everything though. NetFilter is aware of sessions so we can specify that traffic for pre-existing sessions is permitted:

iptables -A FORWARD -i $IF_PUB -o $IF_PRV -m state --state ESTABLISHED,RELATED -j ACCEPT

This adds a rule to the filter table’s forwarding chain. The rule applies to traffic inbound on the public interface and outbound on the private interface. The rule loads the NetFilter “state” module and restricts the rule to operating on packets in the established session state and in the related session state. Matching packets are accepted for routing. The “related” session state is for cases where there is a secondary channel that is associated with the permitted outbound session, e.g. the data connection on a ftp session.

The Firewall Can Be Trusted

Well, if we didn’t trust the firewall we shouldn’t be using it as a firewall. The firewall needs to be able to access other networks.

First we’ll deal with the the loopback (lo) interface. We can just allow everything to and from it:

iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

The rules should explain themselves but we can see that the first accepts traffic in the input chain of the filter table where the traffic is arriving at the lo interface. The second accepts traffic in the output chain of the filter table where the traffic is being output on the lo interface.

Equally, we want to allow the firewall to communicate with hosts on the private network:

iptables -A INPUT -i $IF_PRV -s $NET_PRV -j ACCEPT
iptables -A OUTPUT -o $IF_PRV -d $NET_PRV -j ACCEPT

The first causes the firewall to accept incoming traffic on the private interface that has a source on the private network. The second causes the firewall to permit outgoing traffic on the private interface that has a destination on the private network.

We can’t use the same rules on the public interface because they would allow any traffic to connect to the firewall. On the other hand, we want the firewall to have unrestricted access to public networks:

iptables -A OUTPUT -o $IF_PUB -j ACCEPT
iptables -A INPUT -i $IF_PUB -m state --state ESTABLISHED,RELATED -j ACCEPT

This allows the firewall to send traffic via the public interface to anywhere but, it restricts incoming traffic to only that for existing connections or related to existing connections. So, this allows the firewall to make a connection to some public host and for the traffic in that connection to be received but connections cannot be made to the firewall from the public network.

Note the difference between the private and public interface rules. We assume that anyone on the private network can be trusted and no-one on the public network can be trusted.

Routing For The Private Network

You’ll notice that the private network is a non-public routed IP network. This requires address translation at a router with a public IP address or nothing on the public network will be able to return packets to the private network. Address translation is easily enabled with iptables. The addresses that are being translated are the “source” of sessions so the mode is called Source NAT (SNAT):

iptables -t nat -A POSTROUTING -s $NET_PRV -o $IF_PUB -j SNAT --to $IP_PUB

Note that we specified the table in this rule, -t nat. The rule doesn’t apply to the default, filter table. The rule is applied in the NAT table’s post-routing chain. In other words, after the routing decision has been made. The rule applies to packets with a source address on the private IP network that are being output on the public interface. The action of the rule is to perform SNAT and change the packet’s source IP address to that of the public interface.

You might wonder why there is no rule to perform the reverse translation. In order for that rule to work, NetFilter would have to know which packets to apply it to. The only sensible place to apply it is the session itself. Since NetFilter has to maintain a table of these anyway, there is no need for the reverse rule and the translation is automatically reversed on received packets in the session.

Philosophical Matters

So far we’ve dealt with all the things that we implicitly trust and implicitly distrust. Before deciding what to allow controlled access to I need to express my opinion on reasonable responses to provide to unaccepted traffic. So far, our policy is to drop packets we don’t accept into a black hole. It’s probably the most common behaviour for firewalls. It reveals no information to the sender of the traffic. But, it is often described as being a useful throttle on attackers who are forced to wait timeouts for responses. That was my philosophy but it is no longer. I read a Usenet post some time ago, around the time I was building my first iptables firewall. The post responded to these very points (reveal nothing, delay attacker) by offering the view that attackers are operating mass parallel scans so the delay is moot. It also offered the opinion that a properly protected host won’t reveal anything useful to an attacker anyway so it made more sense to be a compliant IP citizen. I subscribe to both of these positions and my firewall responds as an unimpeded IP stack to most common scanning methods.

ICMP

I don’t permit all ICMP traffic but I do permit ping, for example. Using the ICMP echo request is the simplest form of host scanning. If the host exists and has an unfettered IP stack then it will respond to the sender with an ICMP echo reply. Therefore, ping can be used to discover if an IP address is assigned to a host that is up. It may reveal the presence of my host but that will ultimately be revealed anyway by the web and mail servers I’m going to expose so why lose the utility of ping. The ability to use ping to discover a host isn’t the only problem exposed by ping. Ping can be used as a denial of service (DoS) target. A host can be pinged to death by an attack where multiple remote hosts simultaneously ping the same host (your firewall). NetFilter has a module called limit that can be used to set restrictions on the amount of permitted traffic of some kind. So, my ICMP rules are:

iptables -A INPUT -p icmp --icmp-type 0 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type 3 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type 11 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type 8 -m limit --limit 1/second -j ACCEPT

The type code for ICMP echo requests is 8. As you can see, the rule for those packets loads the limit module and sets a limit of 1 packet per second. The other type codes are echo response (0), destination unreachable (3) and time exceeded (11). These aren’t rate limited because they don’t cause the host to respond (as echo request does). The echo and destination unreachable types may seem obvious but it may not seem obvious why the time exceeded type is permitted. The time exceeded ICMP message is used to return time-to-live (TTL) exceeded errors. An IP packet has a field containing a number called the time-to-live. Every time a packet crosses a router the TTL is decremented. When the TTL reaches zero, the packet will not be forwarded any further and an ICMP time exceeded message is returned to the sender.

You might notice that these rules apply to all interfaces, not just the public one. I didn’t have a specific reason to do that but don’t feel it will have any measurable impact on the firewall or private hosts.

Services On The Firewall Host

So far, the firewall can make its own connections and will forward connections for the private network. It will allow returning traffic for established sessions both to the firewall itself and to hosts on the private network. It will also allow any traffic from the private network to the firewall. Now I want to expose a service on the firewall to the public network. It will be convenient for me to be able to have a ssh session to the firewall from the public side. Just to be a little tricky, I won’t use the standard ssh port (22) and use 2202 instead:

iptables -A INPUT -i $IF_PUB -p tcp -d $IP_PUB --dport 2202 -j ACCEPT

This permits traffic arriving at the public interface with the firewall’s public IP address as a destination and being tcp protocol with a destination port of 2202. Note that the ssh server had to be separately configured to listen on port 2202.

Just to be a little more careful we could rate limit connection attempts:

iptables -A INPUT -i $IF_PUB -p tcp -d $IP_PUB --dport 2202 -m limit --limit 1/minute --limit-burst 1 -j ACCEPT

This will limit ssh connections to one per-minute average with a (burst) limit of one at a time. The more obvious use of limit-burst is where you want to enforce an average rate but don’t mind permitting a short burst from time-to-time. This would, for example, make it easier to deal with connection failures that happen at or near the start of a session. In this case I wanted to limit ssh connections to an average of 1 per minute and not permit more than one in a burst.

Services On The Private Network

I have a mail server and a web server but both are on the private network. I want them to be exposed to the public network but I only want the specific services to be exposed. I only have one public IP address (the private network has non-public routed addresses) and I don’t want to run those services on the firewall. The traditional name for the solution to this problem is “port forwarding”. The NAT router listens on the relevant port on its public interface and forwards any incoming connections or traffic to a destination on the private network, translating the IP destination address to the private target on inbound packets and the IP source address to the firewall’s public address on outbound packets. iptables calls this functionality Destination NAT (DNAT). We expose the mail server as follows:

iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport smtp -j DNAT --to 192.168.1.254
iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -i $IF_PUB -p tcp --dport smtp -j ACCEPT

and the web server:

iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport http -j DNAT --to 192.168.1.253
iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -i $IF_PUB -p tcp --dport http -j ACCEPT

There are two rules for each service, one to perform the address translation and one to permit the routing of the packets. The DNAT rule is added to the PREROUTING chain of the NAT table. It applies to traffic arriving at the public interface with the firewall’s public IP address as the destination. For tcp protocol traffic to a destination port of smtp (25) a DNAT translation is performed to re-target the traffic to a destination address of 192.168.1.254 on the private network. The second rule permits new connections and traffic for established sessions through the router when they arrive at the public interface and are tcp protocol packets with a destination port of smtp (25). The http case is the same but with the destination port changed to http (80).

Note that we were able to use the /etc/services name for the port (smtp, http, etc). Although the forwarding rule specified NEW,ESTABLISHED,RELATED only the NEW and ESTABLISHED states are meaningful for smtp and http.

You can repeat these examples for other protocols, e.g. pop3 to the mail server:

iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport pop3 -j DNAT --to 192.168.1.254
iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -i $IF_PUB -p tcp --dport pop3 -j ACCEPT

Logging The Rest

Since that’s all I want to permit then it would be useful to know what is being blocked. We can add a logging rule to see that:

iptables -A INPUT -i $IF_PUB -j LOG --log-prefix="INPUT   "
iptables -A OUTPUT -o $IF_PUB -j LOG --log-prefix="OUTPUT  "
iptables -A FORWARD -j LOG --log-prefix="FORWARD "

Any packet not yet handled by the previous rules will be logged based on which chain in the filter table it is observed at. Note that there are only logging rules for the filter table. There’s no need for logging rules in the other tables since anything that doesn’t match in those will reach one of the chains in the filter table anyway. These rules will pre-fix the log entry with the name of the chain it was logged in. The log file is /var/log/firewall and automatically rolls over when it gets large. The previous log is re-named to firewall-date and gzipped. The log entries are a single line per item, see the following example of two lines from my log:

Oct 25 16:46:46 firewallhostname kernel: INPUT   IN=eth1 OUT= MAC=00:03:47:af:35:9f:00:d0:88:01:00:95:08:00 SRC=10.0.0.155 DST=10.0.0.1 LEN=48 TOS=0x00 PREC=0x00 TTL=127 ID=31207 DF PROTO=TCP SPT=1645 DPT=135 WINDOW=64240 RES=0x00 SYN URGP=0

Oct 25 16:46:47 firewallhostname kernel: INPUT   IN=eth1 OUT= MAC=00:03:47:af:35:9f:00:d0:88:01:00:95:08:00 SRC= 10.0.0.155  DST= 10.0.0.1  LEN=48 TOS=0x00 PREC=0x00 TTL=127 ID=31307 DF PROTO=TCP SPT=1645 DPT=135 WINDOW=64240 RES=0x00 SYN URGP=0

This shows two attempts by the host at 10.0.0.155 to connect to port tcp/135 (Windows networking) on my firewall. Both entries were logged in the INPUT chain of the filter table.

Whereas the -j ACCEPT rules in the filter table stop the packet from being further processed in the chain containing that rule, the -j LOG rules do not. The packet gets logged and it continues to be processed in the current chain.

When There Is Too Much Being Logged

My firewall is connected to a network that also hosts a DHCP server. If I use the logging as shown above then every DHCP packet gets logged. The network in question is very busy with DHCP traffic so it pollutes the log badly. So, before specifying the log rules I insert the following rule:

iptables -A INPUT -i $IF_PUB -p udp --sport bootps -j DROP

This causes all UDP traffic with the DHCP source port to be dropped in the bit bucket. Adding that rule before the logging removes the DHCP traffic from the logs. If there is other traffic polluting your logs and is not relevant to your monitoring then add similar rules before the logging rules.

Before Applying The Policy

The next part of my script reflects the philosophy I described above. If I added no more rules then the remaining packets would reach the policy for the filter chain and would all be dropped. However, I already have open ports on the router so it removes nothing from the discovery process if I now drop remaining packets. That’s part of my reason for permitting ping. So, I chose to be a good TCP citizen as well:

iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset

Any remaining packets that are TCP protocol will be rejected with a TCP reset (RST) message. This is the normal behaviour of the TCP protocol when a connection is received on a port that has no listening service.

Anything left over is handled by the policy and dropped. I should also add a rule to handle UDP in the similarly “standard” way by responding with a ICMP port unreachable message.

Blocking Specific Problem Sources

You may find that your firewall or other logs indicate you are being repeatedly accessed by some host in some manner that is not permitted. For example, there may be a host poking your web server with requests your web server doesn’t have content for. Your mail filtering may be handling lots of unsolicited traffic from one source. You can use NetFilter to cut them off at the pass so to speak. The cost is that those sources are now blocked from legitimate use of your services. I find that to be a tolerable expense, after all, the source is trying to attack me.

I put my blocking rules at the very top of my rule chains. That way the traffic they filter is discarded first and before any other processing that I know isn’t applicable to it anyway. Also, the blocking rules have to be high enough that they aren’t subverted by rules that identify the traffic and re-route it. For example, it is pointless attempting to block all traffic from a specific IP address at a later rule than an accept rule that will match the same traffic for a different reason (e.g. smtp accept).

I have three blocking rule sets on my firewall. One for protocols, one for source networks and one for source hosts. I added the protocols one most recently to exclude protocols known to be used maliciously. For example, I block IRC completely because it is frequently used to control botnets and I have no current use for IRC:

iptables -A INPUT -p tcp --dport irc -j DROP
iptables -A INPUT -p udp --dport irc -j DROP
iptables -A INPUT -p tcp --dport irc-serv -j DROP
iptables -A INPUT -p udp --dport irc-serv -j DROP
iptables -A INPUT -p tcp --dport ircs -j DROP
iptables -A INPUT -p udp --dport ircs -j DROP

These discard TCP and UDP IRC, IRC server and Secure IRC traffic.

An example of my rule to block a specific IP host is:

iptables -A INPUT -i $IF_PUB -s 10.220.231.236 -j REJECT --reject-with icmp-host-prohibited

This rejects traffic arriving on the public interface from IP address 10.220.231.236. I chose to reject the traffic by responding that the host is blocked, you could just drop the packet instead and make your firewall a black hole for the problem host.

A blocked network is similar:

iptables -A INPUT -i $IF_PUB -s 10.67.232.0/24 -j REJECT --reject-with icmp-net-prohibited

This one rejects traffic arriving at the public interface that has a source address on the 10.67.232.0/24 network. This time the traffic is rejected with an ICMP network prohibited message.

Monitoring a NetFilter Firewall

Now that the firewall is configured we need to consider how we monitor it for problems. I’ve already shown the logging that can be generated but iptables can be used to dump the statistics for the various rules:

iptables -L -v
iptables -t nat -L -v 

The first command provides a verbose list of the rules in each chain in the (default) filter table. The verbose listing provides the hit and byte counts for the rules. The second command does the same for the rules in the chains of the NAT table. Here’s example output for the filter table:

Chain INPUT (policy DROP 2707 packets, 1083K bytes)
 pkts bytes target  prot opt in   out  source         destination
    0     0 DROP    tcp  --  any  any  anywhere       anywhere       tcp dpt:irc
    0     0 DROP    udp  --  any  any  anywhere       anywhere       udp dpt:irc
    0     0 DROP    tcp  --  any  any  anywhere       anywhere       tcp dpt:irc-serv
    0     0 DROP    udp  --  any  any  anywhere       anywhere       udp dpt:irc-serv
    0     0 DROP    tcp  --  any  any  anywhere       anywhere       tcp dpt:ircs
    0     0 DROP    udp  --  any  any  anywhere       anywhere       udp dpt:ircs
    0     0 REJECT  all  --  eth1 any  10.67.232.0/24 anywhere       reject-with icmp-net-prohibited
    0     0 REJECT  all  --  eth1 any  10.220.231.236 anywhere       reject-with icmp-host-prohibited
 2169  524K ACCEPT  all  --  lo   any  anywhere       anywhere
 226K   38M ACCEPT  all  --  eth0 any  192.168.1.0/24 anywhere
 224K   26M ACCEPT  all  --  eth1 any  anywhere       anywhere       state RELATED,ESTABLISHED
    0     0 ACCEPT  icmp --  any  any  anywhere       anywhere       icmp echo-reply
    0     0 ACCEPT  icmp --  any  any  anywhere       anywhere       icmp destination-unreachable
    0     0 ACCEPT  icmp --  any  any  anywhere       anywhere       icmp time-exceeded
14647  640K ACCEPT  icmp --  any  any  anywhere       anywhere       icmp echo-request limit: avg 1/sec burst 5
    9   432 ACCEPT  tcp  --  eth1 any  anywhere       10.0.0.1       tcp dpt:2202
 4350 1459K DROP    udp  --  eth1 any  anywhere       anywhere       udp spt:bootps
 3576 1103K LOG     all  --  eth1 any  anywhere       anywhere       LOG level warning prefix `INPUT   '
  950 47785 REJECT  tcp  --  any  any  anywhere       anywhere       reject-with tcp-reset

Chain FORWARD (policy DROP 7 packets, 1400 bytes)
 pkts bytes target  prot opt in   out  source         destination
1302K  681M ACCEPT  all  --  eth1 eth0 anywhere       anywhere       state RELATED,ESTABLISHED
1229K  253M ACCEPT  all  --  eth0 eth1 anywhere       anywhere
  411 21920 ACCEPT  tcp  --  eth1 any  anywhere       anywhere       state NEW,RELATED,ESTABLISHED tcp dpt:smtp
    1    48 ACCEPT  tcp  --  eth1 any  anywhere       anywhere       state NEW,RELATED,ESTABLISHED tcp dpt:pop3
  681 39308 ACCEPT  tcp  --  eth1 any  anywhere       anywhere       state NEW,RELATED,ESTABLISHED tcp dpt:http
    0     0 LOG     all  --  any  any  anywhere       anywhere       LOG level warning prefix `FORWARD '

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target  prot opt in   out  source         destination
 2169  524K ACCEPT  all  --  any  lo   anywhere       anywhere
 246K   23M ACCEPT  all  --  any  eth0 anywhere       192.168.1.0/24
 220K   43M ACCEPT  all  --  any  eth1 anywhere       anywhere
    0     0 LOG     all  --  any  eth1 anywhere       anywhere       LOG level warning prefix `OUTPUT  ' 

And for the NAT table:

Chain PREROUTING (policy ACCEPT 238K packets, 25M bytes)
 pkts bytes target  prot opt in   out  source         destination
  407 21724 DNAT    tcp  --  eth1 any  anywhere       10.0.0.1 tcp   dpt:smtp to:192.168.1.254
    1    48 DNAT    tcp  --  eth1 any  anywhere       10.0.0.1 tcp   dpt:pop3 to:192.168.1.254
  681 39308 DNAT    tcp  --  eth1 any  anywhere       10.0.0.1 tcp   dpt:http to:192.168.1.253

Chain POSTROUTING (policy ACCEPT 74279 packets, 5074K bytes)
 pkts bytes target  prot opt in   out  source         destination
16058  979K SNAT    all  --  any  eth1 192.168.1.0/24 anywhere to:10.0.0.1

Chain OUTPUT (policy ACCEPT 57342 packets, 4244K bytes)
 pkts bytes target  prot opt in   out  source         destination         

You can see the number of packets and bytes that matched the policy and the number of packets and bytes that matched each rule for each chain in each table. We can look for some of the highlights and decide if some rules can be optimised. You can see that the firewall has observed 253MB of traffic forwarded from my private network and seen 681MB incoming for sessions established from my private network. It’s interesting that the out/in ratio should be so high. You would expect most traffic to be small outbound requests and large inbound responses. My mail and web servers aren’t attracting a lot of traffic.

Take a look at how useful my drop bootp (DHCP) rule is. It dropped 4350 packets. If the rule did not exist or was after the log rule then I’d have 4350 extra log lines that were of no interest.

Leftovers

You can use iptables to create rules that accept input on one-port and re-direct the traffic to a different port:

iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport 444 -j DNAT --to 192.168.1.254:443
iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -o $IF_PRV -p tcp --dport 443 -j ACCEPT

Imagine there’s a SSL web interface to the mail server on 192.168.1.254. Let’s say you already have something using port 443 on the firewall, say SSL access to the web server on 192.168.1.253. Well, we can use iptables to make port 444 on the public side of the firewall transfer traffic to the https port on the mail server. Now, we can access the web mail at http://firewall.public.address:444/

The Final Script

So, here’s a init ready script for SuSE Linux versions to configure a firewall as described above. Create /etc/init.d/firewall and paste the following text into it then save it. Change the file’s mode to executable and use chkconfig firewall on to enable the script at init time (/etc/init.d/firewall start to start the script now). Be sure to disable any other firewall script if you use this one:

#! /bin/bash
# Copyright (c) 2005
#
# Author: David Mair
#
# /etc/init.d/firewall
#
### BEGIN INIT INFO
# Provides: firewall
# Required-Start: $network syslog
# Required-Stop:
# Should-Stop:
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: Firewall configuration
### END INIT INFO


##############################################################################
# DEFAULT POLICY
SetDefaultPolicy() {
	# Drop everything
	iptables -P INPUT DROP
	iptables -P OUTPUT DROP
	iptables -P FORWARD DROP
}


##############################################################################
# FLUSH TABLES
FlushTables() {
	iptables -F -t nat
	iptables -F -t mangle
	iptables -F -t filter
	iptables -X
}


##############################################################################
# ROUTING
EnableRouting() {
	echo 1 > /proc/sys/net/ipv4/ip_forward
}

DisableRouting() {
	echo 0 > /proc/sys/net/ipv4/ip_forward
}


##############################################################################
# FORWARDING
SetForwardingRules() {
	iptables -A FORWARD -i $IF_PUB -o $IF_PRV -m state --state ESTABLISHED,RELATED -j ACCEPT
	iptables -A FORWARD -i $IF_PRV -o $IF_PUB -j ACCEPT
}


##############################################################################
# LOOPBACK
SetLoopbackRules() {
	# Allow everything
	iptables -A INPUT -i lo -j ACCEPT
	iptables -A OUTPUT -o lo -j ACCEPT
}


##############################################################################
# PRIVATE INTERFACES
SetPrivateInterfaceRules() {
	# Allow everything
	iptables -A INPUT -i $IF_PRV -s $NET_PRV -j ACCEPT
	iptables -A OUTPUT -o $IF_PRV -d $NET_PRV -j ACCEPT
}


#############################################################################
# PUBLIC INTERFACES
SetPublicInterfaceRules() {
	iptables -A INPUT -i $IF_PUB -m state --state ESTABLISHED,RELATED -j ACCEPT
	iptables -A OUTPUT -o $IF_PUB -j ACCEPT
}


##############################################################################
# SOURCE NAT
EnableSourceNAT() {
	# Then source NAT everything else
	iptables -t nat -A POSTROUTING -s $NET_PRV -o $IF_PUB -j SNAT --to $IP_PUB
}


# Various ICMP
SetICMP_Open() {
	iptables -A INPUT -p icmp --icmp-type 0 -j ACCEPT
	iptables -A INPUT -p icmp --icmp-type 3 -j ACCEPT
	iptables -A INPUT -p icmp --icmp-type 11 -j ACCEPT
	iptables -A INPUT -p icmp --icmp-type 8 -m limit --limit 1/second -j ACCEPT
}


# SSH (on a non-standard port)
SetSSH_Open() {
	iptables -A INPUT -i $IF_PUB -p tcp -d $IP_PUB --dport 2202 -j ACCEPT
}


##############################################################################
# Destination NAT

# smtp
SetSMTP_DNAT() {
	iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport smtp -j DNAT --to 192.168.1.254
	iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -i $IF_PUB -p tcp --dport smtp -j ACCEPT
}


# pop3
SetPOP3_DNAT() {
	iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport pop3 -j DNAT --to 192.168.10.254
	iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -i $IF_PUB -p tcp --dport pop3 -j ACCEPT
}


# Webmail (444->443)
SetWebmail_DNAT() {
	iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport 444 -j DNAT --to 192.168.10.254:443
	iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -o $IF_PRV -p tcp --dport 443 -j ACCEPT
}


# http
SetHTTP_DNAT() {
	iptables -t nat -A PREROUTING -i $IF_PUB -d $IP_PUB -p tcp --dport http -j DNAT --to 192.168.10.253
	iptables -A FORWARD -m state --state NEW,ESTABLISHED,RELATED -i $IF_PUB -p tcp --dport http -j ACCEPT
}


# Blocked protocols
SetBlockedProtocols() {
	# Block all normal irc (used by botnets)
	iptables -A INPUT -p tcp --dport irc -j DROP
	iptables -A INPUT -p udp --dport irc -j DROP
	iptables -A INPUT -p tcp --dport irc-serv -j DROP
	iptables -A INPUT -p udp --dport irc-serv -j DROP
	iptables -A INPUT -p tcp --dport ircs -j DROP
	iptables -A INPUT -p udp --dport ircs -j DROP
}

# Blocked hosts
SetBlockedHosts() {
	iptables -A INPUT -i $IF_PUB -s 10.220.231.236 -j REJECT --reject-with icmp-host-prohibited
	iptables -A FORWARD -i $IF_PUB -s 10.220.231.236 -j REJECT --reject-with icmp-host-prohibited
}


# Blocked networks
SetBlockedNetworks() {
	iptables -A INPUT -i $IF_PUB -s 10.220.232.0/24 -j REJECT --reject-with icmp-net-prohibited
	iptables -A FORWARD -i $IF_PUB -d $IP_PUB -s 10.220.232.0/24 -j REJECT --reject-with icmp-net-prohibited
}


# Specify things to drop before logging
SetPrelogDropRules() {
	# DHCP
	iptables -A INPUT -i $IF_PUB -p udp --sport bootps -j DROP
}


# Log those on the public interface
SetLoggingRules() {
	iptables -A INPUT -i $IF_PUB -j LOG --log-prefix="INPUT   "
	iptables -A OUTPUT -o $IF_PUB -j LOG --log-prefix="OUTPUT  "
	iptables -A FORWARD -j LOG --log-prefix="FORWARD "
#	iptables -t nat -A PREROUTING -i $IF_PUB -j LOG --log-prefix="nPre    "
#	iptables -t nat -A POSTROUTING -o $IF_PUB -j LOG --log-prefix="nPost   "
#	iptables -t nat -A OUTPUT -o $IF_PUB -j LOG --log-prefix="NAT OUT "
}


# Drop them all
SetDropRules() {
	# Reset tcp connection attempts on all other ports
	# This is the standard TCP behaviour for a closed port. Reading
	# suggests there is no value in stealthing ports and since some are
	# open on this host it doesn't seem to matter. Therefore, let's be a
	# good TCP citizen
	iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
}


##############################################################################
# SCRIPT ENTRY POINT

echo -n "Firewall configuration..."
echo $1

##############################################################################
# ENVIRONMENT

# Private interface
IF_PRV=eth0
IP_PRV=192.168.1.1
NET_PRV=192.168.1.0/24

# Public interface
IF_PUB=eth1
IP_PUB=10.0.0.1
NET_PUB=10.0.0.0/24

# Others
ANYWHERE=0.0.0.0/0

. /etc/rc.status
rc_reset


##############################################################################
# COMMAND LINE

case "$1" in
	start)
		SetDefaultPolicy
		FlushTables

		EnableRouting

		SetBlockedProtocols
		SetBlockedNetworks
		SetBlockedHosts

		SetForwardingRules

		SetLoopbackRules
		SetPrivateInterfaceRules
		SetPublicInterfaceRules

		EnableSourceNAT

		SetICMP_Open
		SetSSH_Open

		SetSMTP_DNAT
		SetPOP3_DNAT
		SetWebmail_DNAT
		SetHTTP_DNAT

		SetPrelogDropRules
		SetLoggingRules
		SetDropRules
		;;

	stop)
		SetDefaultPolicy
		FlushTables

		SetPrivateInterfaceRules
		SetPublicInterfaceRules
		;;

	restart)
		$0 stop
		$0 start
		;;

	*)
		;;
esac

rc_exit
Share
(Visited 17 times, 1 visits today)

Leave a Reply

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

No comments yet

Avatar photo
30,807 views