My-Tiny.Net :: Networking with Virtual Machines



Virtual Private Networks



Stunnel and native capabilities are fine for per-service security, but what if we want to secure all communications between clients and servers? (Not everyone will remember https://). What if we need to provide secure remote access to a lot of different applications?

Best answer: a "Virtual Private Network" (VPN)

VPNs can be used as building blocks to construct anything from a small secure telecommuting solution to a large-scale secure WAN. VPNs tie together concepts from cryptography, networking, and firewalls. OpenVPN is a SSL VPN which can accommodate a wide range of configurations, including remote access, site-to-site VPNs, WiFi security, and remote access with load balancing, failover, and fine-grained access-controls.

In a VPN, the computers at each end of the tunnel encrypt the data entering the tunnel and decrypt it when it arrives. In this sense it is very much like stunnel. The big difference is that stunnel does this at layer 4 (port forwarding), while the VPN works at either layer 3 or layer 2.

A "kernel space" VPN uses the secure version of IP (IPSec) to encrypt the data in every packet before it is sent. This works for both IPv4 and IPv6 networks. IPSec actually consists of three main protocols:
  • IPsec Authentication Header (AH),
  • IPsec Encapsulating Security Payload (ESP), and the
  • IPsec Internet Key Exchange (IKE).
which are described in RFC4301 to RFC4309, plus 27 additional RFCs to clarify and update.
For an excellent explanation of the basics (from the older RFCs) see https://cdn.ttgtmedia.com/searchEnterpriseLinux/downloads/Kozierok_Ch29.pdf

A "user-space" application like OpenVPN essentially links a local TUN or TAP virtual network adapter with a remote one of the same type. IP packets are encrypted and then encapsulated in UDP, and sent over the internet to the corresponding device on the remote host, which receives, decrypts, authenticates, and de-encapsulates the packets.

A program uses a TUN or TAP device to read and write packets, similar to a named pipe (see Standard I/O::Redirection). When a program opens /dev/net/tun, the driver creates and registers a new virtual device with the kernel. When the program closes the device, the driver will automatically delete it and all routes corresponding to it. For background on how this works, see Device Drivers under Linux Notes on the menu.

So, the big question: What is the difference between a TUN device and a TAP device? Simple: TUN works with IP datagrams (layer 3), TAP works with Ethernet frames (layer 2). This makes the two major techniques for making a VPN routing (TUN) and bridging (TAP).

Bridging is good for software that depends on LAN broadcasts such as Windows NetBIOS file and printer sharing. However, bridging is less efficient than routing and does not scale well. Generally people recommend using routing unless there is a particular reason not to.

OpenVPN: TLS vs. Static Key mode

The big thing to watch out for with OpenVPN documentation is that the configuration files use the directives server network netmask (for TUN) and server-bridge gateway netmask pool-start pool-end (for TAP) to have the OpenVPN server manage its own IP address pool and allocate addresses to connecting clients.

The tricky part is that about a meter into the man page it says SSL/TLS authentication must be used in this mode To use TLS, each peer that runs OpenVPN must have its own local certificate/key pair (cert and key), signed with a ca root certificate. That creates some extra key management work.

An alternative is to use a pre-shared static key. Static key configurations are simple to set up (good). There is no signature or feature (such as a header or protocol handshake sequence) that marks the ciphertext packets as being generated by OpenVPN (good). However, the same secret key must exist in plaintext form on each VPN peer, so if an attacker manages to steal the key, everything that was ever encrypted with it is compromised (bad). Static key configurations also have limited scalability: one client, one server (never mind).

This means you need to be careful with configuration examples: server and server-bridge cannot be used with static key mode, and there is no need for the ca, cert, key, and dh parameters - in static key mode the secret parameter is used to designate the shared key that must must be in plaintext form on each VPN peer.

Getting Started

Static key mode fits the TinyNet philosophy of "Do one thing at a time: focus on the essentials first, then build up and out with enhancements".

In this example, we set up a VPN using Static Key mode between two hosts named mon.tn and fri.tn - it is probably better to use names that are in /etc/dnsmasq/cnames already, or the actual IP addresses of the machines you are connecting.

If there is not one already, make a directory to store OpenVPN keys and configuration files - we will use /etc/openvpn

The first step is to create the encryption key on one of our hosts with openvpn --genkey --secret static.key
and set proper permissions with chmod 770 /etc/openvpn/*.key (if necessary). OpenVPN reads the key when it is running as root so the owner doesn't really matter.

Next, copy the .key file to the other host using [F9] and Shell Link in mc (see the section VM to VM with mc and SSH in the Shared Folders page under Orientation on the menu. This only needs to be done once.

Every time we use OpenVPN we need to make sure the proper device driver kernel module is loaded with

modprobe -v tun

Normally we put this into a startup file like /etc/rc.d/ko.local (called by rc.modules), or /etc/rc.d/rc.openvpn. The tun device driver kernel module handles both TUN and TAP devices, and using modprobe when it is already loaded will not cause any problems.

After that, the essential configuration is actually quite simple. There are only four required directives for using OpenVPN in Static Key mode, shown below in configuration files for TUN and TAP on two hosts:

(Sample config files are in /usr/local/etc/openvpn on every Tinynet VM)
VPN Host fri   VPN Host mon
  # OpenVPN tun-fri-mon.conf
    remote mon.tn
    secret static.key
    dev tun
    ifconfig 192.168.86.81 192.168.86.18
<TUN>
  # OpenVPN tun-mon-fri.conf
    remote fri.tn
    secret static.key
    dev tun
    ifconfig 192.168.86.18 192.168.86.81
  # OpenVPN tap-fri-mon.conf
    remote mon.tn
    secret static.key
    dev tap
    ifconfig 192.168.86.81 255.255.255.0
<TAP>
  # OpenVPN tap-mon-fri.conf
    remote fri.tn
    secret static.key
    dev tap
    ifconfig 192.168.86.18 255.255.255.0
  1. The remote directive takes one parameter, the IP address or DNS name of the remote VPN endpoint
  2. The secret directive is the full path to the shared encryption key file
  3. The dev directive is the device: tun or tap
  4. The ifconfig directive takes two parameters:
      * first, the IP address or DNS name of the local VPN endpoint
      * then,
    • For TUN devices, the IP address of the remote VPN endpoint.
    • For TAP devices, the subnet mask of the local VPN endpoint address.
      (In a full setup, this is the subnet for the bridged ethernet segment
      defined in the bridge-setup script described below).

Important:  The addresses used in the configuration file should be private IP addresses which are not in any subnet being used. (Simple, and so easy to get wrong ...)


Since we only have to type it once in the configuration file, add some additional directives to each one:
# Switch UID and GID to "nobody" after initialization for extra security.
  user nobody
  group nobody

# Message detail ('verb'osity) - this may or may not be the syslog level ...
# 0 = quiet except for fatal errors
# 1 = mostly quiet, but display non-fatal network errors (default)
# 3 or 4 = medium output, good for normal operation
# 5 = output R or W for each packet read or write
#     uppercase for TCP/UDP packets, lowercase for TUN/TAP packets
# 6 to 9 = verbose, good for troubleshooting
  verb 3
Now to start OpenVPN all we need to do is put the name of the configuration file on the command line, along with the cd directive to tell OpenVPN to change directory before reading any configuration files, key files, and scripts. This way we can put all of the OpenVPN control files in one location. cd takes an absolute path with a leading / and without any . or .. references to the current directory.

on fri:    openvpn --cd /etc/openvpn --config tun-fri-mon.conf
on mon: openvpn --cd /etc/openvpn --config tun-mon-fri.conf

This (properly modified for our cnames) will be just enough to verify the tunnel is working: we should be able to ping the first IP in the ifconfig directive from the other host. (Note: sometimes it takes a little while for the connection to get settled, especially the second or third time you connect. Check the log file, htop, ifconfig, and netstat while you wait.)

The problem with this method is that we need to do the command very quickly on both hosts. When OpenVPN starts from the command line it tries to connect to the peer immediately and will time out if it cannot. In this bare-bones mode it will also time out if there is no traffic.

OpenVPN and xinetd

The ideal tool to solve the race condition that comes from starting two peers is a xinetd listener, just like the one in Utilities::Multitail on the menu: it will (patiently) listen and start the local side when it receives a connection request from a remote host.

The only thing we need to take care of is when we run multiple instances of OpenVPN we need a separate (tun/tap) adapter and a separate port for each connection. If we don't want them to talk to each other we also need to ensure each adapter has a unique, non-overlapping subnet.

Having a set of ports is no problem: looking in /etc/services we find there is only one tcp+udp port for OpenVPN (1194), but there are a couple of useful blocks of tcp+udp ports that are assigned to services we are highly unlikely to use ...
timbuktu-srv1 1417
timbuktu-srv2 1418
timbuktu-srv3 1419
timbuktu-srv4 1420
timbuktu was a remote desktop
(screen-sharing) client/server
application that was discontinued
in 2015.

netview-aix is network monitoring
software for the IBM version of Unix
(AIX), that runs mainly on IBM hardware.
netview-aix-1  1661
 ...2 through 8...
netview-aix-9  1669
netview-aix-10 1671
netview-aix-11 1672
netview-aix-12 1673
Let's set up the xinetd listener for a tun connection on host mon (Do Not try this on the TinyNet Gateway - use two of the others or create new VMs configured as NoRole).

Get /etc/xinetd.d/ on both sides in mc. Use [F5] to copy and rename telnet (just type the new filename in the destination field). The filename needs to match the service and id directives, so be sure to decide in advance.

Just a few quick edits and we're ready. Note that user is a required directive, and we leave it as-is because OpenVPN needs to access its privileged port before it switches to nobody.
    service timbuktu-srv1
    {
        id            = timbuktu-srv1	
    #    flags         = NAMEINARGS
        socket_type   = dgram
        protocol      = udp
        wait          = yes
        user          = root
        server        = /usr/sbin/openvpn
        server_args   = --inetd --cd /etc/openvpn --config tun-mon-fri.conf
    #    only_from     = 127.0.0.1
        disable       = no
    }
The OpenVPN configuration file needs one small change: comment out the remote directive, because it conflicts with the --inetd directive in the server_args line in the xinetd configuration file.

Now restart xinetd and check the new service with netstat.

On the remote system (fri), from the command line or a shell script:

openvpn --port 1417 --daemon --cd /etc/openvpn --config tun-fri-mon.conf

The daemon directive frees the terminal after all of the initialization functions are completed, and sends all message and error output to syslog using the daemon facility (the level is undocumented, but notice works fine). The port can be specified by number or its name in /etc/services

To check the connection, from host fri, ping the first IP in the ifconfig directive in the config file on mon (192.168.86.18 in the example). That's enough to show that our tun-to-tun connection is ready to go!

Use Ctrl c to stop ping and use htop to kill the client-side OpenVPN process.

The steps are essentially the same to bring up a tap connection. On host mon, get /etc/xinetd.d/ on both sides in mc, and use [F5] to copy and rename timbuktu-srv1 to netview-aix-1 (for example). Edit the new copy to change the service and id to match the filename, and change the name of the config file in server_args to tap.

In the configuration file you specified, comment out the remote directive like before, and also comment out the last two lines because we don't need a bridge until we start "Getting Real" (see below). Now restart xinetd.

On host fri, be sure to use htop to kill the OpenVPN tun process. Use [Up-Arrow] to get the OpenVPN connect command back, change the port, change the name of the config file to tap, and run it. Then ping the first IP in the ifconfig directive in the config file on mon (192.168.86.18 in the example).


Important:  To use both tun and tap connections at the same time, adjust the IP addresses in the server and client config files to give the tun and tap tunnels different endpoints.


To summarise, using UDP and Static Key mode you need a separate file in /etc/xinetd.d for each connection, the filename must match the service name, and you need to specify the port when you connect from the remote system. Other configurations using TLS mode and/or TCP are described in the OpenVPN documentation.

Getting Real

So far we have covered the very basics - just enough to get a tun-to-tun or tap-to-tap connection. But that's not quite enough to be really practical - to really use the VPN a bit more needs to be done with the startup files.
  • For tun, start IP forwarding between interfaces and set a static route
  • For tap, create a virtual ethernet bridge on the server side

Routed VPN (tun)

To talk with the networks behind your VPN server, the first step is to add a static route in the configuration file.
                xinetd                      script
   +-------------+   +-------+       +-------+   +-------------+
   | mon (net-b) |---| <tun>  [<< >>]  <tun> |---| fri (net-c) |
   +-------------+   +-------+       +-------+   +-------------+
   192.168.66.0/24       [192.168.86.0/24]       192.168.76.0/24
The OpenVPN route directive adds a route to the routing table after the connection is established. Multiple routes can be specified. There are two keywords we can use to make things simple.

On fri (client side), we derive the gateway from the
second parameter to ifconfig
On mon (xinetd side) we do the same for the
fri subnet, and read the default gateway
from the routing table for other subnets
  # OpenVPN tun-fri-mon.conf
    remote mon.tn
    secret static.key
    dev tun
    ifconfig 192.168.86.81 192.168.86.18
route 192.168.66.0 netmask 255.255.255.0 vpn_gateway

  # OpenVPN tun-mon-fri.conf
    remote fri.tn
    secret static.key
    dev tun
    ifconfig 192.168.86.18 192.168.86.81
route 192.168.76.0 netmask 255.255.255.0 vpn_gateway
route 192.168.56.0 netmask 255.255.255.0 net_gateway

With those directives in our OpenVPN configuration files, the last step is to turn on IP forwarding between the TUN interfaces and the regular ethernet interfaces. The easiest way to do this is put /etc/rc.d/rc.ip_forward start in your /etc/rc.d/rc.openvpn

Bridged VPN (tap)

Bridging is not as easy to set up as routing, but the scripts in /etc/openvpn/sample-scripts take care of the gnarly part.

An ethernet bridge is essentially the software equivalent of a physical ethernet switch. Each connected interface corresponds to one "switch port" for the bridge. Network traffic coming in on any of these ports will be forwarded to the other (layer 2) ports transparently, which is the same thing that turning on IP forwarding does for our (layer 3) TUN configuration.

The ethernet bridge interface must be created, and the relevant ethernet interfaces added to it, before OpenVPN is actually started. This requires the Linux bridge-utils package, which is part of the mytyVM core. The IP address and subnet of the bridge interface cannot be with an ifconfig directive in the OpenVPN config file, like we can for the TAP interface - they must be set in a separate script.

Usually the bridge is only configured on the server side, not the client side. The clients will be "multi-homed" when they connect to the server: they have their regular ethernet interface plus a TAP interface that is bridged with the server's ethernet interface when the OpenVPN connection is established.

This is the gnarly part: it takes several (executable, mode 755) scripts to set up the bridge interface and add the interfaces to the bridge.
  1. Set up the bridge interface (mkbr.sh)
  2. Add a physical ethernet adapter to the bridge (ctlbr.sh)
  3. Add each TAP adapter to the bridge (tap-server.up)
#!/bin/bash
# Bridging setup script 
# ORDER IS IMPORTANT: run this BEFORE rc.xinetd
  # load the ethernet bridging kernel module
    modprobe -v bridge
  # create a bridge interface named br0
    brctl addbr br0
  # set the 'bridge forward delay' to [n seconds]
    brctl setfd br0 0

  # bridge configuration - DO NOT use this IP in OpenVPN directives
    ifconfig br0 192.168.86.1 netmask 255.255.255.0 broadcast 192.168.86.255
The next step is to add a physical ethernet adapter to the bridge. This means that all frames received on ifname will be processed as if destined for the bridge, and ifname will be considered as a potential output interface when sending frames on brname.

IMPORTANT:
  • The address used in the remote directive MUST be joined to the bridged interface but MUST NOT be part of the bridged subnet - otherwise we would end up with a routing loop.
  • The physical adapter should have its IP address and netmask BEFORE it is added to the bridge
  • Only add the physical adapter AFTER the IP address and netmask of the bridge interface is set
#!/bin/bash
# essential control of the ethernet bridge 
# ORDER IS IMPORTANT: 'addif' AFTER bridge setup and BEFORE rc.xinetd
  case "$1" in
    'addif')
       echo "Adding interface $2 to bridgeif $3"
       brctl addif $3 $2
    ;;
    'showall')
       echo "Status of all bridge instances"
       brctl show
    ;;
    'showbr')
       echo "Status of bridge instance $2"
       brctl show $2
    ;;
    'delif')
       echo "Removing interface $2 from bridgeif $3"
       brctl delif $2 $3
    ;;
    'delbr')
       echo "Stopping bridge instance $2"
       ifconfig $2 down 
       brctl delbr $2
    ;;
    *)
       echo "Usage: $0 {addif if br|showall|showbr br|delif if br|delbr br}"
  esac
Finally, to join the tap interface to the bridge interface we need to modify the ifconfig directive in the TAP configuration file to call an up script. OpenVPN requires a separate directive to allow an external script to be run: the default for script-security is level one which only allows OpenVPN internal hooks to operating system commands like ifconfig, and it must be set at level two to allow our up script to run.

So, using the diagram above, the config file on mon needs to be changed like this:
  # OpenVPN tap-mon-fri.conf
    remote fri.tn
    secret static.key
    dev tap
    ifconfig 192.168.86.18 255.255.255.0
    script-security 2
    ifconfig-noexec
    up tap-server.up
The up script looks like this:
#!/bin/bash
  # OpenVPN tap-server.up
  # add interface to bridge and activate
  #     Details of the positional parameters 
  #     are hard to find in the documentation!
    brctl addif br0 $1
    ifconfig $1 up
And that does it. Now we have a real VPN that can access hosts on the server subnet, not just the server itself.



Details of the positional parameters are hard to find in the documentation!
  # command line passed to the shell by ifconfig-noexec   
  #     %0  script_name
  #     %1  device (from dev directive)
  #     %2  device_mtu 
  #     %3  link_mtu
  #     %4  local_ip (from ifconfig directive)
  #     %5  remote_ip || netmask (from ifconfig directive)
  #     %6  init || restart (default: init)


Useful Resources

See the Examples section of the man page at 
      https://linux.die.net/man/8/openvpn
      for a tunnel with full TLS-based security and IPtables firewall rules
Some nice tricks
      http://workaround.org/openvpn-faq
A very complete and extensively commented config file for multi-client server and for the clients too!
      http://www.commentcamarche.net/forum/affich-5348139-open-vpn
Three interesting scenarios
      http://www.wains.be/index.php/2008/07/18/openvpn-routing-all-traffic-through-the-vpn-tunnel/
NICE discussion of openvpn push v. dhcp
      http://hans.fugal.net/blog/2009/01/06/putting-openvpn-in-its-place/