The following tip was developed using Ubuntu 9.1x (Hardy Heron) with OpenVPn 2.1rc19. It builds on the the setup from Part I.
This post has been updated since publication to include FORWARD directives for the strangers list as well.
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 destinationAs 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
COMMITRestart 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 destinationNow 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.
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_DoeYou 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 addressesThis 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/0As 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_DoeYou 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/0You 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_StrangerYou 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 sharesA 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:445For 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_StrangerYou 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/0You 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!
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:
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:
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.1When you’re finished testing, make sure to remove the hack.
Finally, here are samples of all of the files modified in this tutorial. See Part I for the other files.