Awasu » Banana Pi gateway: Setting up a firewall
Tuesday 1st March 2016 9:26 PM []

Of all the extra services we can have on our gateway, a firewall is the most useful and should be considered mandatory. In this section, we will block all incoming and outgoing traffic, and then selectively enable only the traffic we want to allow e.g. web browsing, email, etc.

Before we start, it's important to understand the different types of messages that can be sent over a network:

TCP messages

These are sent between two computers who have set up a connection between themselves, and are usually request/response i.e. one computer sends the other one a message, which then sends back a reply.

UDP messages

These are "shouted out" on the network, to whoever might be listening[1]There is no guarantee that UDP messages will get through, they could (and often do) get lost.. In this case, there is no concept of request/response, it's just computers broadcasting messages to the network to whoever might be there.

ICMP messages

These are low-level messages that computers send to each other to pass information about the state of the network itself.

We will use the iptables program to manage the firewall, which uses tables and chains. A chain is just a list of rules that are checked, in order, to see if a message should be allowed through, or blocked. There are several standard tables, but we will only be using the following:

filter 

This table is used when checking whether to allow packets in or out, and has the following chains:

  • INPUT: a list of rules that control what packets are allowed in
  • OUTPUT: a list of rules that control what packets are allowed out
  • FORWARD: a list of rules that control what packets can be forwarded on
NAT

This table is used to control NAT, and we will use only the following chain:

  • POSTROUTING: allows packets to be modified after they have been routed

So, for example, if a program running on a computer on the internal network wants to send out an email, it must go through the gateway[2]Since this is the only way it can get online., and iptables will go to the filter table and check all the rules in the OUTPUT chain, looking for one that specifies whether outgoing email is allowed or blocked.

Basic configuration

WARNING! It is very easy to lock yourself out of the computer if you make a mistake configuring the firewall. If you're using a keyboard and monitor that have been plugged in, it doesn't matter, but if you're connecting via SSH and accidentally block SSH traffic, you will no longer be able to communicate with the computer!

This is especially bad if you've set the firewall rules to be applied when the system boots up, since even a reboot won't clear the problem - SSH will still be blocked and so the only solution will be to plug in a keyboard and monitor, or restore the SD card from a backup. For this reason, it's a good idea to disable firewall configuration in /etc/rc.local when you're working on it, until you're sure everything is OK.

We will start[3]The usual way of managing iptables configuration is by using iptables-save and iptables-restore, but I prefer to use a shell script, since I find it easier to see what's happening, and it's scriptable. by configuring the firewall to block everything, and log anything that gets blocked:

# clear out any existing configuration
iptables --flush
iptables --delete-chain

# set the default policy to DROP
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

# TODO: allow selected traffic through

# create a custom chain that logs packets, then drops them
iptables -N LOGDROP
# nb: the first "limit-burst" packets will be logged, then only "limit" per minute
iptables -A LOGDROP \
    -m limit --limit-burst 10 --limit 10/m \
    -j LOG --log-prefix "iptables: "
iptables -A LOGDROP -j DROP

# log and drop any remaining packets (nb: this must appear last)
iptables -A INPUT   -j LOGDROP
iptables -A OUTPUT  -j LOGDROP
iptables -A FORWARD -j LOGDROP

The first few lines reset everything, then set the default policy[4]This tells iptables what to do with a packet if it reaches the end of a chain without finding a rule that tells it what to do. to DROP.

Near the end of the script, we create a custom chain called LOGDROP, and add a rule[5]-A means "append a rule to the chain". to it to log the packet[6]-J LOG means "log the packet".[7]We also limit how frequently messages can be logged (in the event we are under attack, we don't want to log huge amounts of duplicate messages), and prefix them with "iptables: "., and then another rule that drops the packet.

At the very end of the script, we add a final rule to each of the filter chains that tell iptables to jump to the LOGDROP chain. In other words, for each packet, iptables will scan a chain looking for a rule that allows or rejects it (we will add these next), but if it doesn't find a match, the last rule tells it to jump to the LOGDROP chain, which will log the packet, and then drop it.

In the example screenshot, rules have been added to the INPUT chain to allow incoming email and SSH, otherwise iptables will jump to the LOGDROP chain, where it will log the packet then drop it. Similarly, the OUTPUT chain has been configured to allow outgoing email, HTTP and SSH traffic, and everything else will get logged and dropped.

Allowing TCP responses

The first type of traffic we want to allow through are TCP responses. If a request has been issued by a computer on the internal network, we always want to allow its response to come back in:

iptables -A INPUT   -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT

So, we append a rule to the INPUT chain (-A INPUT) that says: for TCP traffic[8]Only TCP has the concept of request/response; UDP is broadcast only. (-p tcp), check the connection's state (-m state) and if it's either ESTABLISHED[9]We've seen at least one packet on this connection already i.e. the request. or RELATED[10]The connection is related to a previous request e.g. FTP, which needs 2 connections to work., then allow the packet through (-j ACCEPT).

We also add a similar rule to the OUTPUT chain i.e. if we've already allowed a request to come in from outside, then allow the response to go out:

iptables -A OUTPUT  -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT

And also to the FORWARD chain:

iptables -A FORWARD -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT

Allowing loopback traffic

Next, we allow all traffic on the loopback interface i.e. if a program on the computer is trying to communicate with another program on the same computer, always let that traffic through:

iptables -A INPUT  -i lo -j ACCEPT

In other words, append a rule to the INPUT chain (-A INPUT) that says: traffic coming in on the loopback interface (-i lo) should be allowed (-j ACCEPT).

Similarly, we add a rule to the OUTPUT chain that allows all traffic going out on the loopback interface:

iptables -A OUTPUT -o lo -j ACCEPT

Allowing ICMP traffic

ICMP traffic is most commonly known as used by ping, but there's a whole lot more to it than that. In particular, DNS will get slow if you block it, so while there are certain types of attack that can be made using ICMP traffic, for a home gateway, you're probably better off just allowing everything:

iptables -A INPUT   -p icmp -j ACCEPT
iptables -A OUTPUT  -p icmp -j ACCEPT
iptables -A FORWARD -p icmp -j ACCEPT

Allowing DNS traffic

DNS uses UDP, so to allow outgoing DNS traffic, we need to add a rule that allows UDP messages to go in and out on port 53 :

iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT  -p udp --sport 53 -j ACCEPT

However, certain types of DNS traffic uses TCP, so we add a rule that allows TCP traffic to go out on port 53, but only for new connections:

iptables -A OUTPUT -p tcp --dport 53 -m state --state NEW -j ACCEPT

Note that there's no need to add a rule to allow the responses back in, since we added a generic rule earlier that allows all traffic for ESTABLISHED connections.

We add similar rules to allow incoming DNS traffic:

iptables -A INPUT  -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p udp --sport 53 -j ACCEPT
iptables -A INPUT  -p tcp --dport 53 -m state --state NEW -j ACCEPT

Allowing DHCP traffic

DHCP also runs over UDP (on ports 67 and 68), so while we could add rules similar to those we added above for DNS, we want to add an extra restriction: only allow this traffic for computers on the internal network.

My computers connect to the gateway either via wired ethernet (on the eth0 interface) or via WiFi (on the wlan1 interface), so my rules look like this:

iptables -I INPUT  -i eth0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I OUTPUT -o eth0 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I INPUT  -i wlan1 -p udp --dport 67:68 --sport 67:68 -j ACCEPT
iptables -I OUTPUT -o wlan1 -p udp --dport 67:68 --sport 67:68 -j ACCEPT

My gateway computer connects to the internet via the wlan0 interface, but since the rules above are explicitly for the eth0 and wlan1 interfaces, any DHCP traffic on the wlan0 interface will be dropped.

Allowing SSH traffic

I only ever want to SSH in[11]I never SSH out from that computer. to my gateway computer, from another computer on my internal network, so my rule for SSH traffic looks like this:

iptables -A INPUT  -i eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT

For safety, I explicitly allow incoming traffic for ESTABLISHED connections, and I also explicitly allow outgoing traffic:

iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

These rules are important - if I make a mistake here, I will get locked out! :wall:

Allow incoming HTTP traffic

If you've set up the DNS-based ad-blocker, you will need to allow incoming requests to the pixel server:

iptables -A INPUT  -i eth0 -p tcp --dport 80 -m state --state NEW -j ACCEPT

Allow incoming email traffic

If you've set up email relaying, you will need to allow incoming email:

iptables -A INPUT  -i eth0 -p tcp --dport 25 -m state --state NEW -j ACCEPT

Note that it's critical to only allow this traffic from the local network, otherwise somebody from the outside could connect to your mail server and use it to send spam.

Allowing other common traffic

The following rules allow outgoing HTTP/HTTPS traffic[12]These are using for browsing the web.:

PORTS=http,https
iptables -A OUTPUT -p tcp -m multiport --dports $PORTS -m state --state NEW -j ACCEPT

Note that we can use descriptive names for the ports[13]These can be found in /etc/services. and again, we allow outgoing traffic for new connections only, responses are allowed by the generic "allow responses" rule defined earlier.

We also want to allow NTP traffic[14]This is used to synchronize clocks., which runs over UDP:

PORTS=ntp
iptables -A OUTPUT -p udp -m multiport --dports $PORTS -j ACCEPT
iptables -A INPUT  -p udp -m multiport --sports $PORTS -j ACCEPT

Note that these rules apply only to traffic that originates on the gateway computer; for traffic that originates on another computer on the internal network, we need to add rules to the NAT table, which we'll do next.

Allowing common traffic over NAT

As described in the section on setting up NAT, we need a rule to enable NAT:

# clear out any existing configuration
iptables -t nat --flush
iptables -t nat --delete-chain

# enable NAT
iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE

We can then allow outgoing traffic[15]This rule controls traffic coming in on the wired network (via eth0), and going out to the internet (via wlan0). for selected services:

PORTS=http,https,pop3,pop3s,imap,imaps,smtp,smtps,587,ssh,ftp
iptables -A FORWARD \
    -i eth0 -o wlan0 \
    -p tcp -m multiport --dports $PORTS \
    -m state --state NEW \
    -j ACCEPT

Similarly, we allow UDP traffic for selected services:

PORTS=ntp
iptables -A FORWARD \
    -i eth0 -o wlan0 \
    -p udp -m multiport --dports $PORTS \
    -j ACCEPT
iptables -A FORWARD \
    -i eth0 -o wlan0 \
    -p udp -m multiport --sports $PORTS \
    -j ACCEPT

I also allow web traffic only from my WiFi-connected devices:

PORTS=http,https
iptables -A FORWARD \
    -i wlan1 -o wlan0 \
    -p tcp -m multiport --dports $PORTS \
    -m state --state NEW \
    -j ACCEPT

Allowing VPN traffic

If you are running OpenVPN, you will need the following rules to allow it to work:

iptables -A OUTPUT -o wlan0 -p udp --dport 1194 -j ACCEPT
iptables -A INPUT  -i wlan0 -p udp --sport 1194 -j ACCEPT

If you are running OpenVPN, any rules that specify the interface you use to connect to the internet (in my case, wlan0), need to be changed to refer to OpenVPN's tunnel interface instead i.e. tun0. The script supplied below takes care of this.

Putting it all together

There are a lot of rules described here, and the situation is complicated by OpenVPN, since the rules are different depending on whether or not you are using it, so I've put together a shell script[16]Since this script sometimes needs to start OpenVPN, there is another script called openvpn.sh that starts OpenVPN with your configuration. that takes care of everything, and can either:

  • disable the firewall
  • enables the firewall (with OpenVPN running)
  • enables the firewall (with OpenVPN not running)

You will obviously want to customize the rules for your own situation, but it's a good starting point, and once you've got it working the way you want, add it to /etc/rc.local so that your rules will be applied when the system boots up.

Monitoring blocked packets

At the very beginning of this section, we set things up so that any dropped packets get logged. By default, these appear in /var/log/messages, but since there will be a lot of these messages, it's far more convenient to collect them in a separate file. To do this, create the file /etc/rsyslog.d/iptables.conf that looks like this:

:msg, contains, "iptables: " -/var/log/iptables
& ~

This tells the logging daemon (rsyslogd) to look for messages that contain the string "iptables: " and send them to another file (/var/log/iptables).

Since we've created a new log file, it's a good idea to set up log rotation for it. Create the /etc/logrotate.d/iptables file like this:

/var/log/iptables
{
    rotate 7
    daily
    missingok
    notifempty
    delaycompress
    compress
    postrotate
        invoke-rc.d rsyslog rotate > /dev/null
    endscript
}

Finally, the log file contains a lot of information about each packet that we're usually not interested in, so this script will filter out the less-important stuff and provide an easier-to-read log.

« Setting up an email relay

Tutorial index

 

   [ + ]

1. There is no guarantee that UDP messages will get through, they could (and often do) get lost.
2. Since this is the only way it can get online.
3. The usual way of managing iptables configuration is by using iptables-save and iptables-restore, but I prefer to use a shell script, since I find it easier to see what's happening, and it's scriptable.
4. This tells iptables what to do with a packet if it reaches the end of a chain without finding a rule that tells it what to do.
5. -A means "append a rule to the chain".
6. -J LOG means "log the packet".
7. We also limit how frequently messages can be logged (in the event we are under attack, we don't want to log huge amounts of duplicate messages), and prefix them with "iptables: ".
8. Only TCP has the concept of request/response; UDP is broadcast only.
9. We've seen at least one packet on this connection already i.e. the request.
10. The connection is related to a previous request e.g. FTP, which needs 2 connections to work.
11. I never SSH out from that computer.
12. These are using for browsing the web.
13. These can be found in /etc/services.
14. This is used to synchronize clocks.
15. This rule controls traffic coming in on the wired network (via eth0), and going out to the internet (via wlan0).
16. Since this script sometimes needs to start OpenVPN, there is another script called openvpn.sh that starts OpenVPN with your configuration.
Have your say