The iptables software is a user space application for configuring packet filtering in the Linux kernel. iptables is used to set rules for packets that travel through a host’s network stack and at certain points, called hooks, the iptables rules are evaluated and actions, such as dropping a packet, can be executed. In this post I will draft a script to setup the iptables’ rules for a firewall and then set that script to be executed at boot time.
iptables conceptually consists of tables of chains (rules). The default table is the filter table, and that’s where our firewall rules will be written. Let’s have a look at the current iptables rules. The -S
option tells iptables to list the current rules.
> sudo iptables -S -P INPUT ACCEPT -P FORWARD ACCEPT -P OUTPUT ACCEPT
This is what it looks like when there are no rules in the filter table. The -P
lines tell us the policy for a given chain. A chain is a list of firewall rules. INPUT
, FORWARD
, and OUTPUT
are the default chains and the user can create their own chains if they wish.
The default chains nicely separate network traffic concerns. The INPUT
chain is for inbound datagrams, the OUTPUT
chain is for datagrams going out from the host, and the FORWARD
chain is for datagrams that come into the host machine and then go out.
If you already have rules in place, you can flush them with the following commands:
> iptables -F > iptables -X
The -F
option tells iptables to flush (delete all the rules) a given chain. Since none is given, iptables flushes all chains. -X
deletes a given chain. Since none is given it deletes all non built-in chains. We’ll add that to the script.
#!/bin/bash ipt="/sbin/iptables" # Flush Tables $ipt -F $ipt -X # Setup Policies $ipt -P INPUT DROP $ipt -P FORWARD DROP $ipt -P OUTPUT ACCEPT
If we run our script now, we set the policies — with the -P
option — for the default chains of the filter table. DROP tells the kernel to simply do nothing with the packet and it dies. This won’t allow any packets to go past the lower levels of the network stack onto applications, including packets sent to the loopback interface. We’ll add a rule to allow all loopback traffic.
# Accept Packets on the Loopback Interface $ipt -A INPUT -i lo -j ACCEPT
The -A
option is for appending to a given (INPUT
in this case) chain; the -i
is for ingress interface, lo
matches the loopback interface on the Linux machine; and -j
jumps to the target of the rule; e.g. if the packet matches the rule condition -i lo
then -j ACCEPT
the packet, or allow it to pass through the firewall. This is an simple explanation, there’s a bit more going on and I encourage anyone interested in adding on to this simple script follow the links in this post to learn more.
We also want traffic that was initiated by the host machine to be allowed to pass through the firewall. Right now, packets are allow to go out to the world, and responses to those packets cannot get back in. We’ll use the connection tracking system (part of the same netfilter framework that iptables is), which keeps track of all sessions, to allow established — the firewall has seen two-way communication — and related — expected — connections.
# Allow expected and two-way communication. $ipt -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
Running our script will configure a basic firewall. Things are allowed out, but only host generated traffic is allowed in. This even includes ICMP packets, so if you try to ping this host, their will be no response (if we would have opted to use the REJECT
function for our INPUT chain policy, a message would be sent in reply stating the host is unreachable).
We’ll add a rule to the INPUT chain to reply to pinging, and using the same form of iptables command we’ll add a rule to allow inbound traffic to our OpenSSH server.
#!/bin/bash ssh_port=22 ipt="/sbin/iptables" # Flush Tables $ipt -F $ipt -X # Set up Policies $ipt -P INPUT DROP $ipt -P FORWARD DROP $ipt -P OUTPUT ACCEPT # Load Modules $ipt -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # Accept Packets on the Loopback Interface $ipt -A INPUT -i lo -j ACCEPT ## Open Ports ## # icmp $ipt -A INPUT -p icmp --icmp-type echo-request -j ACCEPT # ssh $ipt -A INPUT -i eth0 -p tcp --dport $ssh_port -m state --state NEW,ESTABLISHED -j ACCEPT
Our basic firewall has been drafted. To allow further inbound services, just follow the same pattern as for ICMP and the SSH services. The last thing left to do is set the script to run at boot time. To do this we add the following line to our root crontab.
> sudo crontab -e
# Edit this file to introduce tasks to be run by cron. # # Each task to run has to be defined through a single line # indicating with different fields when the task will be run # and what command to run for the task # # To define the time you can provide concrete values for # minute (m), hour (h), day of month (dom), month (mon), # and day of week (dow) or use '*' in these fields (for 'any').# # Notice that tasks will be started based on the cron's system # daemon's notion of time and timezones. # # Output of the crontab jobs (including errors) is sent through # email to the user the crontab file belongs to (unless redirected). # # For example, you can run a backup of all your user accounts # at 5 a.m every week with: # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ # # For more information see the manual pages of crontab(5) and cron(8) # # m h dom mon dow command @reboot /usr/local/scripts/firewall.sh
Voila, a basic extensible iptables firewall that runs at boot time.