Setting up an iptables firewall

From OpenVZ Virtuozzo Containers Wiki
Revision as of 18:10, 16 March 2009 by PSUdaemon (talk | contribs) (Setting up a firewall that allows per-container configuration)
Jump to: navigation, search

This document consists of two parts. The first is setting up a firewall (using iptables) on the HN, which will restrict traffic to the containers. The effect would emulate, as far as the containers and their customers are concerned, an external hardware firewall controlled by the sysadmin. The second is setting up a firewall that protects the HN itself but still allows traffic to the containers, thus allowing individual containers to define their own iptables.

While the firewalls shown here can be accomplished using iptables manually (or using Fedora core's iptables service), the methods presented here are especially modular and easy to modify. This is important when you have 20+ containers and a lot of other things to be doing...

The scripts and pathnames given here are for Fedora Core 6, though they can probably be applied to most similar SysV-like systems with little modification.

A little background

On our systems, we use the HN to provide privileged services which are not appropriate for access by the containers. For example, the HN acts as a backup server, runs Nagios for health monitoring, has a webserver for managing the 3ware RAID controller, etc. The containers are leased to customers, who can't entirely be trusted, especially if they get hacked. As such, our scenario is one in which the HN must be protected from all access (even from the containers) except for a few trusted hosts (e.g. my home-office).

The exception to this is the nameserver, which we want open to the world. We use it as a caching nameserver for our containers and also to host DNS for a few customer domain.

Setting up a HN-based firewall

This setup emulates (to the containers anyway) an external hardware firewall. It protects the HN from any access and then defines what services and ports are allowed/banned for individual containers. This leaves the firewall controlled by the site administrator, not be individual containers and the hackers who've gotten into them. ;)

First off, let's disable Fedora's existing iptables service:

service iptables stop
chkconfig iptables off

Now create the new firewall service. This code should be /etc/init.d/firewall and then should be chmod'd 755.

#!/bin/sh
# firewall      Start iptables firewall
# chkconfig: 2345 08 92
# description:  Starts, stops and saves iptables firewall
# This script sets up the firewall for the INPUT chain (which is for
# the HN itself) and then processes the config files under
# /etc/firewall.d to set up additional rules in the FORWARD chain
# to allow access to containers' services.

. /etc/init.d/functions

# the IP block allocated to this server
SEGMENT="192.168.0.0/24"
# the IP used by the hosting server itself
THISHOST="192.168.0.1"
# services that should be allowed to the HN;
# services for containers are configured in /etc/firewall.d/*
OKPORTS="53"
# hosts allowed full access through the firewall,
# to all containers and to this server
DMZS="12.34.56.78 90.123.45.67"

purge() {
  echo -n "Firewall: Purging and allowing all traffic"
  iptables -P OUTPUT ACCEPT
  iptables -P FORWARD ACCEPT
  iptables -P INPUT ACCEPT
  iptables -F
  success ; echo
}

setup() {
  echo -n "Firewall: Setting default policies to DROP"
  iptables -P INPUT DROP
  iptables -P FORWARD DROP
  iptables -I INPUT   -j ACCEPT -m state --state ESTABLISHED,RELATED
  iptables -I FORWARD -j ACCEPT -m state --state ESTABLISHED,RELATED
  iptables -I INPUT -j ACCEPT -i lo
  iptables -I FORWARD -j ACCEPT --source $SEGMENT
  success ; echo

  echo "Firewall: Allowing access to HN"
  for port in $OKPORTS ; do
    echo -n "          port $port"
    iptables -I INPUT -j ACCEPT -s $SEGMENT -d $THISHOST --protocol tcp --destination-port $port
    iptables -I INPUT -j ACCEPT -s $SEGMENT -d $THISHOST --protocol udp --destination-port $port
    success ; echo
  done
  for ip in $DMZS ; do
    echo -n "          DMZ $ip"
    iptables -I INPUT   -i eth0 -j ACCEPT -s $ip
    iptables -I FORWARD -i eth0 -j ACCEPT -s $ip
    success ; echo
  done

  CTSETUPS=`echo /etc/firewall.d/*`
  if [ "$CTSETUPS" != "/etc/firewall.d/*" ] ; then
  echo "Firewall: Setting up container firewalls"
  for i in $CTSETUPS ; do
    . $i
    echo -n "          $CTNAME CT$CTID"
    if [ -n "$BANNED" ]; then
      for source in $BANNED ;  do iptables -I FORWARD -j DROP --destination $CTIP --source $source ; done
    fi
    if [ -n "$OPENPORTS" ]; then
      for port in $OPENPORTS ; do iptables -I FORWARD -j ACCEPT --protocol tcp --destination $CTIP --destination-port $port ; done
      for port in $OPENPORTS ; do iptables -I FORWARD -j ACCEPT --protocol udp --destination $CTIP --destination-port $port ; done
    fi
    if [ -n "$DMZS" ]; then
      for source in $DMZS ; do iptables -I FORWARD -j ACCEPT --protocol tcp --destination $CTIP --source $source ; done
      for source in $DMZS ; do iptables -I FORWARD -j ACCEPT --protocol udp --destination $CTIP --source $source ; done
    fi
    [ $? -eq 0 ] && success || failure
    echo
  done
  fi
}

case "$1" in
  start)
    echo "Starting firewall..."
    purge
    setup
    ;;
  stop)
    echo "Stopping firewall..."
    purge
    ;;
  restart)
    $0 stop
    $0 start
    ;;
  status)
    iptables -n -L
    ;;
  *)
    echo "Usage: $0 <start|stop|restart|status>"
    ;;
esac

The above script can be called like this:

service firewall start
service firewall stop
service firewall restart
service firewall status

It will set up the firewall for the HN according to the parameters you specified for OKPORTS, DMZs, etc. and then it will call each file under /etc/firewall.d and process its configuration.

So create a file under /etc/firewall.d The exact filename isn't important, as long as it's meaningful to you, e.g. ExampleCompany or ve12 and give it content like this:

# This file is processed by /etc/init.d/firewall
CTID="1"			# the container's ID#
CTNAME="Customer1"		# A human-friendly label for the container
CTIP="192.168.1.34"		# the IP address for this container 
OPENPORTS="80 443"		# ports that should be universally opened
				# to the entire Internet
DMZS="1.2.3.0/24 5.6.7.8/32"	# IPs and blocks that should have full access
				# to the container's services
BANNED=""			# IPs and blocks that should be entirely
				# blocked from the container's services

And there you go. Go ahead and start the firewall and check its status:

service firewall restart
service firewall status

As you can see, you can now add and edit the configurations for individual containers very easily. This method proves a lot easier to manage than Fedora's iptables-config mechamism!

Debian Notes

The setup above works fine for Debian as well, however /etc/init.d/functions is missing. Here is a very simple version that you can use:

 # /etc/init.d/functions
 
 success() {
   echo -n "...success"
 } 
 failure() {
   echo -n "...failure"
 }

Setting up a firewall that allows per-container configuration

This setup configures iptables on the HN to disallow access to all hosts, including the containers. However, it allows all traffic into the containers so they may define their own iptables rules and therefore manage their own firewall.

iptables -P FORWARD ACCEPT
iptables -F FORWARD

This will remove all rules for the FORWARD chain so all packets can pass back and forth between containers and the outside world.

If you want to use a firewall inside a container, please load these modules BEFORE starting the container:

modprobe xt_tcpudp
modprobe ip_conntrack

If you do not, you will get an error like this: "iptables: No chain/target/match by that name"

If you want to use stateful firewall rules (and you should!) you will also need to make sure that 'ipt_state' is in the 'IPTABLES' option in your vz.conf file:

IPTABLES="ipt_REJECT ipt_tos ipt_limit ipt_multiport iptable_filter iptable_mangle ipt_TCPMSS ipt_tcpmss ipt_ttl ipt_length ipt_state"

See also