Configuring an iptables firewall on Ubuntu Hardy Heron server
October 31st, 2008
In this article we'll set up a simple firewall on an Ubuntu 8.04 server. The firewall has two purposes:
- Block all ports except the few which are used to provide services
- Map incoming port 80 to port 8080, so that our Java web servers can run as non-root
And all this must be done on a remote server, so we have to do it in a way that doesn't lock us out.
The services we need
We need to open ports for the following:
http, port 80
smtp, port 25
sshd, port 22
smtpfor relaying, we have this on port 10025
imap, TLS encrypted in our case, port 143
ftp: we might decide to enable FTP service
- OpenVPN will also be enabled at some future point
The key thing here is to do these things without getting locked out of the remote server while we're doing them. To that end, the best thing to use is a server on VirtualBox. This allows easy testing of configurations without any risk of being locked out.
Trying ufw: the uncomplicated firewall
Ubuntu 8.04 ships with a tool called ufw, the uncomplicated firewall,
which makes it easier to manipulate firewall rules than
issuing iptables commands directly. It seems like a convenient thing
to use, but I could not get it to be compatible
with NAT port rewriting. Therefore I gave up and
did it the old fashioned way, using the
commands. This is not a problem; the ip-tables commands are easy enough
to use directly.
Using iptables directly
The machine starts out with empty iptables rule chains, meaning that iptables is not active. Start by allowing all traffic on loopback. If you don't do this, various processes, such as Postfix, will break:
iptables -I INPUT 1 -i lo -j ACCEPT
Then create a rule which allows established tcp connections to stay up:
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
Then add the individual ports we want:
iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j ACCEPT
Add a drop-all at the end of the chain for anything which doesn't match one of the accepted ports:
iptables -A INPUT -j DROP
Now that is a basic server firewall configuration. But there's one problem here. Our web servers are all Java-based (JBoss AS or Tomcat). Pure Java applications have no way of binding to port 80 without running as root, because they cannot change user. Apache HTTPD is able to drop root after binding, becaue it can access operating system calls, but Java processes cannot.
Therefore we need some way to run a Java server process and let it have access to port 80. The solution is to use port redirection.
Note that this "privileged ports"
concept has probably caused more security breaches
in the history of Unix / Linux than any other single thing,
and is the oldest design flaw / bug in the Unix / Linux systems.
It should really be removed. Data from the external ports
are the most dangerous and so should be handled with
the least access permissions. What does Unix / Linux do?
The exact opposite. This is unfortunate. But so many
system administrators believe the old nonsense that
if you let non-root process bind to privileged ports something
bad will happen, this bug is here to stay. None of these administrators
who believe this are able to explain exactly what bad thing would happen,
but they continue to believe it. It is actually based
in a strange psychological belief that root is for stuff
that's "important", because root is the most powerful user,
and the most powerful user should be doing the most important
stuff, right? Unfortunately this is completely wrong;
tasks should all happen with the least possible permissions
needed to accomplish the task. The design of Postfix
is a good example of privilege minimization. Postfix
is a set of small, isolated, single-purpose
tasks, with minimal access permissions, not running as root,
Fortunately iptables gets us out of this problem.
Redirecting port 8080 to port 80
Use one command to create a redirection rule:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
But, this rule doesn't work. What happens is that the prerouting rules are all applied before the input chain happens. What this means is that port 80 has already been remapped to port 8080 before the packet is checked on the input chain. This is a problem because there's no rule to allow port 8080. We need to add one, before the drop command.
We can use the insert style of the
command to do this. First use
to list the chain, and insert the rule for 8080 in
some place before the final
iptables -I INPUT 3 -p tcp --dport 8080 -j ACCEPT
This inserts it at position 3. Note that we are not really using the allow rule for port 80, because port 80 is getting mapped to 8080 before the allow rule could trigger. We could go ahead and delete that allow rule. However, it doesn't hurt to leave it there.
Test it. You should be able to get to the Tomcat server, running as non-root on port 8080, from outside, going to port 80. Note that trying to go to Tomcat on port 80 on loopback won't work at this point because there's no rule for it. Add:
iptables -t nat -A OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
Saving the rules
It would be nice if there were some standard rules file to save these into. There is not, not at this time within Ubuntu Server 8.04.
So we save it to our own file:
iptables-save > /etc/iptables.rules
and modify the
pre-up iptables-restore < /etc/iptables.rules
Put this after the
iface eth0 line / stanza.
The complete stanza looks like:
iface eth0 inet static address 333.333.333.333 netmask 255.255.255.0 gateway 333.333.333.1 pre-up iptables-restore < /etc/iptables.rules
Reboot the machine and make sure everything comes up ok. You don't want the machine to be in a non-bootable state once it's in production.
Notes on JBoss Application Server bind addresses
Tomcat, by itself, will bind to the host's external interface address. JBoss AS does not. By default, JBoss AS will only listen for connections on localhost.
What we want is for it to listen on the external interface, on port 8080.