Simple Firewall Configuration Using NetFilter/iptables
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
No comments yet