Your browser may have trouble rendering this page. See supported browsers for more information.

|<<>>|193 of 273 Show listMobile Mode

How to configure a local firewall for OpenVPN (Part II)

Published by marco on

Updated by marco on

The following tip was developed using Ubuntu 9.1x (Hardy Heron) with OpenVPn 2.1rc19. It builds on the the setup from Part I.


Part I of this guide to configuring a local firewall for OpenVPN introduced you to using iptables on Linux. It also included a script for OpenVPN that opened and closed the firewall for specific IP addresses. If you haven’t read it already, you should probably go do that first.

Unfortunately, it turns out that the firewall configuration from part I is not watertight because it still allows FORWARDs for all IP addresses. If you’ll recall, we solved this problem for INPUTs by closing them by default and selectively opening them.

The first step is to ascertain that the firewall is configured as we expect. A call to sudo iptables -nL elicits the following output:

Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

As you can see, the default policy for FORWARD is ACCEPT, which allows anyone to access other IP addresses from this machine. In Part I, you created a file named /etc/iptables.uprules in which you stored the default configuration of the firewall. You’ll want to change that as shown below (the changes are highlighted):

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i eth0 -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A FORWARD -i eth0 -j ACCEPT
-A FORWARD -i lo -j ACCEPT
COMMIT

Restart networking by executing sudo /etc/init.d/networking restart. A call to sudo iptables -nL should now elicit the following output (the main changes are highlighted):

Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Now all IP forwarding requests are blocked by default. Those for the lo and eth0 interfaces are, of course, still enabled, to allow the machine to be reachable both by itself and the local network.

The final step is to change the firewall configuration script to open up IP forwarding for employees, but not for strangers. Since this is a FORWARD rule, not an INPUT one, the script has to make sure the remove all firewall rules for the client IP address instead of just the INPUT rules as it did previously. Changes from the script in Part I are highlighted.

function inlist
{
  less `dirname $0`/$1 | egrep "^${CLIENTCERT}$" > /dev/null
  if [ $? -eq 0 ]; then
    return 0
  else
    return 1
  fi
}

function get_next_matching_firewall_rule
{
  ip_address=$1
  channel=$2

  RULE="`iptables -L $channel -n –line-numbers | grep $ip_address | head -n 1`"
}

function drop_rule_from_iptables
{
  rule="$1"
  channel="$2"
  echo "  Drop rule [$rule] for channel [$channel]"
  line_number=`echo "$rule" | awk '{print $1}'`
  iptables -D $channel $line_number
}

function add_port_to_iptables
{
  source_ip=$1
  destination_ip=$2
  protocol=$3
  port=$4

  iptables -A INPUT -i tun0 -s $source_ip -d $destination_ip -p $protocol –dport $port -j ACCEPT
  iptables -A INPUT -i tun0 -s $source_ip -d $destination_ip -p $protocol –dport $port -j ACCEPT
}

function add_destination_to_iptables
{
  source_ip=$1
  destination_ip=$2
  iptables -A INPUT -i tun0 -s $source_ip -d $destination_ip -j ACCEPT
  iptables -A FORWARD -i tun0 -s $source_ip -d $destination_ip -j ACCEPT
}

function open_firewall_for_strangers
{
  echo "  Add route for DNS"
  add_port_to_iptables $CLIENTIP 192.168.1.1 "UDP" 53

  echo "  Add route for Windows shares"
  add_port_to_iptables $CLIENTIP 192.168.1.5 "TCP" 139
  add_port_to_iptables $CLIENTIP 192.168.1.5 "TCP" 445

  return 0
}

function open_firewall_for_employees
{
  echo "  Add routes for all ip addresses"
  iptables -A INPUT -i tun0 -s $CLIENTIP -j ACCEPT
  iptables -A FORWARD -i tun0 -s $CLIENTIP -j ACCEPT
  return 0
}

function open_firewall
{
  echo "Opening firewall for $CLIENTCERT @ [$CLIENTIP]"
# TODO Add filtering for other lists, if desired
# inlist "MYGROUP.list"
#if [ $? -eq 0 ]; then
#  echo "  Certificate found in MYGROUP list"
#  open_firewall_for_MYGROUP
#  return 0
#else
  inlist "strangers.list"
  if [ $? -eq 0 ]; then
    echo "  Certificate found in strangers list"
    open_firewall_for_strangers
    return 0
  else
    inlist "employees.list"
    if [ $? -eq 0 ]; then
      echo "  Certificate found in employee list"
      open_firewall_for_employees
      return 0
    else
      echo "  Certificate not found in any list"
      return 1
    fi
  fi
}

function close_firewall_channel
{
  channel=$1

  get_next_matching_firewall_rule $CLIENTIP $channel

  while [ -n "$RULE" ]
  do
    drop_rule_from_iptables "$RULE" $channel
    get_next_matching_firewall_rule $CLIENTIP $channel
  done

}

function close_firewall
{
  echo "CloseFirewall for [$CLIENTIP]"

  close_firewall_channel "INPUT"
  close_firewall_channel "FORWARD"
  close_firewall_channel "OUTPUT"
}

# Main

OPERATION=$1
CLIENTIP=$2
CLIENTCERT=$3

case "$1" in
  add)
    close_firewall
    open_firewall
    ;;

  update)
    close_firewall
    open_firewall
    ;;

  delete)
    close_firewall
    ;;
  *)
    echo "Unknown operation"
    exit 1
esac

exit $?

Since you only changed the firewall configuration script, there is no need to restart OpenVPN.

Testing the Script

You can test to verify that the firewall is updated properly by simply executing the /etc/openvpn/configfirewall.sh script with various parameters. The expected parameters are an operation—”add” or “delete” for testing purposes—a name—matched against the names in your lists—and an IP address, which should be chosen so as not to interfere with any addresses assigned by either OpenVPN or a DHCP server.

To test what would happen when an employee connects through OpenVPN, execute the following command:

sudo /etc/openvpn/configfirewall.sh add 192.168.40.3 John_Doe

You should see the following output from the script:

CloseFirewall for [192.168.40.3]
OpenFirewall for John_Doe @ [192.168.40.3]
  Certificate found in employee list
  Add routes for all ip addresses

This sounds about right and it looks like the script ran as expected. You can check that the firewall was configured as expected with a call to sudo iptables -nL, which should now elicit the following output (the main changes are highlighted):

Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  192.168.40.3         0.0.0.0/0

Chain FORWARD (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  192.168.40.3         0.0.0.0/0

As you can see, the firewall accepts all INPUT and FORWARD from employees. Removing this test employee is as simple as executing:

sudo /etc/openvpn/configfirewall.sh delete 192.168.40.3 John_Doe

You should see the following output from the script:

CloseFirewall for [192.168.40.3]
  Drop rule [4    ACCEPT     all  –  192.168.40.3         0.0.0.0/0           ] for channel [INPUT]
  Drop rule [4    ACCEPT     all  –  192.168.40.3         0.0.0.0/0           ] for channel [FORWARD]

A call to sudo iptables -nL should now elicit the following output, where the rules for the employee have been removed:

Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0

You should really test with one user from each list, so the next user to test is a stranger. Add a stranger by calling the script with the strangers’s name instead of the employee’s name:

sudo /etc/openvpn/configfirewall.sh add 192.168.40.3 John_Stranger

You should see the following output from the script:

CloseFirewall for [192.168.40.3]
OpenFirewall for John_Stranger @ [192.168.40.3]
  Certificate found in strangers list
  Add route for DNS
  Add route for Windows shares

A call to sudo iptables -nL should now elicit the following output:

Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     udp  –  192.168.40.3         192.168.1.1       udp dpt:53
ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:139
ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:445

Chain FORWARD (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     udp  –  192.168.40.3         192.168.1.1       udp dpt:53
ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:139
ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:445

For strangers, the firewall accepts only requests on the ports and IP addresses explicitly opened by the script and drops all FORWARD requests. Removing this test employee is as simple as executing:

sudo /etc/openvpn/configfirewall.sh delete 192.168.40.3 John_Stranger

You should see the following output from the script:

CloseFirewall for [192.168.40.3]
  Drop rule [4    ACCEPT     udp  –  192.168.40.3         192.168.1.1       udp dpt:53 ] for channel [INPUT]
  Drop rule [4    ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:139 ] for channel [INPUT]
  Drop rule [4    ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:445 ] for channel [INPUT]
  Drop rule [4    ACCEPT     udp  –  192.168.40.3         192.168.1.1       udp dpt:53 ] for channel [FORWARD]
  Drop rule [4    ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:139 ] for channel [FORWARD]
  Drop rule [4    ACCEPT     tcp  –  192.168.40.3         192.168.1.5       tcp dpt:445 ] for channel [FORWARD]

A call to sudo iptables -nL should now elicit the following output, where the rules for the stranger have been removed:

Chain INPUT (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy DROP)
target     prot opt source               destination
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0
ACCEPT     all  –  0.0.0.0/0            0.0.0.0/0

You can use the script this way to test the firewall configuration without actually logging in through OpenVPN. When everything is set, you should still log with OpenVPN as a user from each list to verify that the firewall is doing what you think it is doing. In fact, that’s exactly why there is a Part II to this article: We tested by adding a user to the strangers list and logging in and noticed that we were able to ping many more servers than we had configured. Don’t let that happen to you!

Testing via OpenVPN

So, there’s one more trick that you can use to make testing via OpenVPN easier. Since you have to be outside the network to test tunneling in via VPN, you run into the problem of testing as a stranger because strangers probably won’t have rights to open a shell on the OpenVPN server. That is, you need to be able to do this:

  1. Log in with VPN as an employee
  2. Add yourself to the strangers list
  3. Log out of VPN
  4. Log in with VPN as a stranger
  5. Test that you cannot access anything that you shouldn’t be able to
  6. remove yourself from the strangers list (uh oh!)

Since you’re a stranger, you can no longer open a shell on the OpenVPN server and alter the configuration.

Here are some ways of getting around this problem:

  • The safest and best way to test via OpenVPN is to create a user exclusively for testing and add that user to each of your lists in turn, logging in and out to test the configuration. If you need to change something, you just log out and log back in using your “employee” user.
  • Another way is to open an OpenVPN session from one machine (it can be a virtual machine) and then add that same user to each of your lists in turn, logging in and out from another machine. Just make sure not to lose the connection from the “main” machine or you may be locked out.
  • Another way around this is to add an exception for the OpenVPN server to all configurations (strangers, employees, etc.) so that you can test almost everything. To do this, just add the a rule for the OpenVPN server (assumed to be on 192.168.1.1) as follows:

    add_destination_to_iptables $CLIENTIP 192.168.1.1

    When you’re finished testing, make sure to remove the hack.

Files

Finally, here are samples of all of the files modified in this tutorial. See Part I for the other files.

  • iptables.uprules: The default rules to apply to the firewall
  • configfirewall.sh: The firewall configuration executed when a user connects or disconnects from OpenVPN