Before we begin it is a good idea to make sure that we are starting from the same point. To that end let's start by creating a simple script which we shall use to configure our firewall.
Type the following into the new script and save it.
#!/bin/bash
iptables -F
iptables -X
Then make it executable and execute it.
The first command in the script will flush all the rules in all the chains. The second command will remove any user-defined chains. Chains cannot be removed if they contain rules hence the flush command.
We can now run the command below to display the status of the chains.
Chain INPUT (policy ACCEPT 546 packets, 47801 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 448 packets, 58973 bytes)
pkts bytes target prot opt in out source destination
As you can see there has been some activity on both the INPUT and OUTPUT chains, in this case because I'm connected to the system using ssh, and their policy is set to ACCEPT. The FORWARD chain however has seen no activity and has its policy set to DROP.
If your FORWARD chain has a policy of ACCEPT, and it probably does as that is the default setting, you can change it to DROP by issuing the following command. You should also add it to the top of the script above so that it will be set when the script is run.
Before we start to configure our firewall it would probably be a good idea to cover how we can generate a log of exactly what effect our rules are having. After all, when we make a mistake, and we will, it would be nice to know what is happening. It is also a relatively simple task which makes it a good introduction to the basic concepts which we shall be building upon as we progress with our configuration.
Add the following lines to the end of the script which we created earlier.
iptables -N LOGDROP
iptables -A LOGDROP -j LOG --log-prefix 'FIREWALL - DROP:' --log-level info
iptables -A LOGDROP -j DROP
Then execute the script and display the Netfilter status as shown below.
Chain INPUT (policy ACCEPT 5505 packets, 582K bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 2073 packets, 772K bytes)
pkts bytes target prot opt in out source destination
Chain LOGDROP (0 references)
pkts bytes target prot opt in out source destination
0 0 LOG all -- any any anywhere anywhere LOG level info prefix `FIREWALL - DROP:'
0 0 DROP all -- any any anywhere anywhere
As you can see the first command in our scriptlet above created a new user-defined chain called LOGDROP. We then added two rules, neither of which has a match specification, to our new chain with the target of LOG and DROP respectively. The output above also shows that this chain is not referenced from any other chains. Also of note is that we specified a prefix for the log messages so that we can easily identify them visually or when using the a system logger such as syslog-ng.
While the rule above is good for packets that we just want to drop and pretend we never saw, sometimes it is useful to send a response indicating why we refused the connection. This can be accomplished using the REJECT target. All the standard ICMP error codes are supported, as well as other responses such as a TCP reset. If the response type is not specified then an icmp-port-unreachable message is sent. We shall be covering the REJECT target in more detail later.
Now that we have the ability to log the results of our activity we can get our hands dirty with some real rules. Lets start with a very simple rule which will allow us to filter any packets which are obviously malformed or invalid in the context which they are received. A good example of such a packet would be a TCP packet without the SYN flag set which is not part of an already established stream.
We can begin to achieve this goal by adding the lines below to the end of our script.
iptables -N TCPCHECK
iptables -A TCPCHECK -p tcp ! --syn -m state --state NEW -j LOGDROP
Now execute the script and display the Netfilter status again as shown below.
...
Chain LOGDROP (1 references)
pkts bytes target prot opt in out source destination
0 0 LOG all -- any any anywhere anywhere LOG level warning prefix `FIREWALL - DROP:'
0 0 DROP all -- any any anywhere anywhere
Chain TCPCHECK (0 references)
pkts bytes target prot opt in out source destination
0 0 LOGDROP tcp -- any any anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN state NEW
When you execute the above commands you should see output similar to that shown above. As you can see the first line of our additions simply created a new user-defined chain called TCPCHECK, just as we did before when creating the LOGDROP chain.
The second line is more complex as it is composed of two match specifiers and a jump target. The first match specifier in this case is composed of two parts. The first part, -p tcp, checks to see if this packet is a TCP packet. If it is then the second part, ! --syn, checks to see if it does not have just the SYN flag set. Any packets which have matched the first specifier will go on to be tested against the second. This match uses the state matcher to see if this packet is considered to be starting a NEW connection. If the packet also matches this second condition then the -j part of the command will cause it to be sent to the LOGDROP chain that we created earlier. As it will encounter a terminating rule in that chain processing of that packet will cease.
The Netfilter connection tracking subsystem comes with a matcher which can be used to detect a limited range of invalid packets. We can include it in the TCPCHECK chain by adding the following line to the end of our script.
iptables -A TCPCHECK -m state --state INVALID -j LOGDROP
Since we have just created a chain to filter malformed TCP packets it would be nice to put it to work as soon as possible. We can achieve this using only the skills we have learnt so far too, so lets get on with it. Add the lines below to the bottom of the script as usual.
iptables -N TCP-IN-FILTER
iptables -A TCP-IN-FILTER -j TCPCHECK
iptables -A INPUT -p tcp -j TCP-IN-FILTER
When you execute the script again and then show the Netfilter status you should see output similar to that given below.
Chain INPUT (policy ACCEPT 26209 packets, 3007K bytes)
pkts bytes target prot opt in out source destination
21 1572 TCP-IN-FILTER tcp -- any any anywhere anywhere
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 6872 packets, 1918K bytes)
pkts bytes target prot opt in out source destination
Chain LOGDROP (1 references)
pkts bytes target prot opt in out source destination
0 0 LOG all -- any any anywhere anywhere LOG level warning prefix `FIREWALL - DROP:'
0 0 DROP all -- any any anywhere anywhere
Chain TCP-IN-FILTER (1 references)
pkts bytes target prot opt in out source destination
21 1572 TCPCHECK all -- any any anywhere anywhere
Chain TCPCHECK (1 references)
pkts bytes target prot opt in out source destination
0 0 LOGDROP tcp -- any any anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN state NEW
0 0 LOGDROP all -- any any anywhere anywhere state INVALID
As you can see from the above output we have achieved a lot with very little work. We have created a new chain called TCP-IN-FILTER, to which we shall add jumps to our input filtering subroutines. We have added such a jump to include the TCPCHECK chain. We have also added a rule to the INPUT chain so that all incoming TCP packets will be sent to the TCP-IN-FILTER chain for processing. You can see from the numbers in the pkts column that it is working. In the above example there have been 21 packets processed so far, none of which were found wanting.
Congratulations! You have just set your first functional Netfilter policy.