Ubuntu Server Project #4: Iptables Firewall

Continuing along the security theme set by the previous article I’ll configure some simple iptables firewall rules for my Ubuntu Server virtual machine. Iptables can be pretty complicated and I won’t attempt to go into great detail. Since this is a virtual machine only accessible from within my home network I have the luxury of being able to play without having to actually be concerned with security. So iptables will be set up for the experience and for future testing.

Iptables is installed with every Ubuntu installation so there’s nothing new to install. We just need to configure the rules that iptables needs to use. Since I’m setting up a web server I’ll create rules to allow SSH (port 22222), HTTP (port 80) and HTTPS (port 443) traffic.

I’m going to create two files that contain the iptables rules. One will be used for testing and the other will be for production. The production rules will be permanent and load during reboots. The test rules will be in file /etc/iptables.test.rules and the production rules will be in file /etc/iptables.prod.rules.

The Rules

I connect to the Ubuntu server using SSH from the terminal on my Mac. Everything done related to iptables has to be done as root so I issue the command:

sudo -i

and enter my password when prompted. Now I won’t have to use sudo as a prefix for each command.

For my first step I’ll save any existing rules to the production file using the command:

iptables-save >/etc/iptables.prod.rules

On my freshly installed Ubuntu server this generated the following file contents:

image lost

 

To list the current filter rules on the screen I run iptables with the -L switch.

iptables -L

which results in the following information:

image lost

 

What the above means is that anything from anyone on any port will be accepted. I’m not a fan of the theory that as long as nothing is running on the ports then nothing needs to be blocked. I am a fan of blocking everything except traffic which this server is intended to handle. So I’ll be setting up some rules to restrict traffic. Initially I’ll be doing this in the /etc/iptables.test.rules file. During this time I’ll keep my existing terminal connection active and actually start a second session just to be sure. This way if a test rule blocks SSH I’ll have an existing connection that I can make the change with. (OK, it’s a VM on my Mac so no second session, but if it was a remote server I’d set up the second session as a safety measure.)

I start off with some very simple rules which are based on information found in the Ubuntu Documentation Iptables HowTo. Rules are processed top to bottom and once a decision is made about a packet no more rules are processed.

A lot of traffic on the server uses the loopback port and we want to allow it all. No reason to stop intra-server communication. So I add the lines:

-A INPUT -i lo -j ACCEPT
-A INPUT -i ! lo -d 127.0.0.0/8 -j REJECT

The first line says to accept all traffic on the loopback port. The second rule says to reject all traffic that uses the loopback address but isn’t on the loopback port. -A means append the rule to the chain. INPUT is the chain to add the rule to. Valid chains are INPUT, FORWARD and OUTPUT as shown in the previous screenshots. -i means to only match if the traffic is on the specified interface. lo is the loopback interface. -j is the action to take with the packet. Valid actions are ACCEPT, REJECT (Reject and notify sender), DROP (silently ignore) and LOG. The ! in the second line means “not” so in this case it means traffic not on the loopback adapter. -d indicates the destination and can be an ip address or port. In this case it’s the loopback address.

Then I’ll add a rule to continue to accept all established connections:

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

State matches are described in greater detail at faqs.org. But this rule says to accept all traffic for an ESTABLISHED connection that has seen traffic in both directions. It will also accept traffic for new connections if it’s associated with an established connection, these are the RELATED packets.

Next I’ll allow all outbound traffic. I’ll leave restricting outbound traffic for another day.

-A OUTPUT -j ACCEPT

Now I’ll enable web traffic on the common ports of 80 for HTTP traffic and 443 for HTTPS traffic.

-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

The -p specifies the connection protocol used, in this case tcp and dport indicates the destination port.

Now I’ll allow SSH traffic. Use the same port specified in the ssh_config file. In my case it was port 22222.

-A INPUT -p tcp -m state --state NEW --dport 22222 -j ACCEPT

In this rule the state parameter is used to allow the creation of NEW connection. The previously defined rule for established connections will apply once the connection is created by this rule.

Next up is a rule to allow pings.

-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

In this rule icmp is the protocol used. A complete list of icmp types is at faqs.org which shows 8 as a “echo request” type.

Now I’ll create a rule to log incoming packets that are denied by iptables.

-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

This rule will log denied packets, up to 5 a minute. It will prefix the log entries with “iptables denied: “. The LOG action doesn’t stop rule processing so the packets will be processed by any following rules. The reason we know these packets will be refused is because the only rules that follow will reject the packet. So if a packet has reached this rule there isn’t a chance for it to be accepted.

So the rules to deny any remaining packets are:

-A INPUT -j REJECT
-A FORWARD -j REJECT

The rules file needs to begin with *filter and end with COMMIT. The complete iptables rules file is available as a text file.

Enforcing the Rules

I save the rules to /etc/iptables.test.rules and then run the following command to load them in:

iptables-restore </etc/iptables.test.rules

The to see if anything actually changed I run iptables -L and compare it to the previous results. As the screenshot below shows they are different.

 

image has been lost

Now it’s time to test the critical SSH connection. I open a new terminal window and try a connection. It works and the other rules seem correct so I’m all set. If it failed I’d still have my existing connection to fix the problem (assuming the rules to allow existing connections took affect).

Now I need to make these rules permanent. First I’ll save them to my production rules file:

iptables-save >/etc/iptables.prod.rules

Now I need to make sure the rules are loaded at startup. I load the file /etc/network/interfaces in the nano editor. I add the following line at the end of the loopback section:

pre-up iptables-restore </etc/iptables.prod.rules

The screenshot below shows my updated interfaces file.

image has been lost

The final test is to restart Ubuntu server and make sure the rules are still in place.

So now I have a basic server setup and it’s running a simple firewall. I’ll probably spend a little time exploring Ubuntu before I start installing the server software.