07 diciembre

OpenSSH based VPNs

The poor man VPN setup

VPN (Virtual Private Network) is probably something most of you already know about. I guess most of you are familiar with terms like ipsec, pptp or OpenVPN and probably some of you know how setting up any of those feels like... yeah, a PITA.

So, this is a post for those who would like to be able to set up a VPN quickly, without having to install additional dependencies or having to mess up with complicated certificates/CAs or configuration files.

What if you could set up a fully functional VPN using just OpenSSH?

The blues fishes, from the OpenBSD project for the release of OpenBSD 5.7

I'm assuming you have a recent version of OpenSSH installed in the systems that will be part of the VPN (most systems have it nowadays) and that you have some knowledge about Unix-like systems and Shell Scripting.

For more details, you can check the manual page of ssh(8) from the OpenBSD project. Look for the section SSH-BASED VIRTUAL PRIVATE NETWORKS.

As you can see in the docs, this setup will work for a point-to-point VPN link between 2 hosts. If you want to add multiple remote hosts to the VPN, you will have to repeat the process for each of the remote hosts.

Long story short, the VPN will be initiated when one of the systems (let's call it client) starts an SSH connection to the other system (let's call it server). When that connection is opened, a virtual network interface is set up, creating the encrypted link between the two points of the VPN.

Preparations: private and public keys for ssh authentication

Before going any further, first thing we have to do is to generate the private and public keys that will be used to authenticate against the OpenSSH server (in case you did not have them already). If you want all the details, take a look at the ssh-keygen man page. Basically we can generate them with:

ssh-keygen -b 2048

This will generate RSA keys, check the man page to see how to choose other types of keys.

Once we have our public key ready, we have to authorize such key in the OpenSSH server. In the home directory of our user in such server, we have to add the public key to the file ~/.ssh/authorized_keys. I'm not going to explain all the details here (there are plenty of tutorials out there about this topic).

Setup the "server"

On the machine that will act as our "server", we need a shell script that will bring our virtual interface up. This is how I do it in OpenBSD:

/sbin/ifconfig tun1 netmask

This brings up a tun interface with an assigned ip address linked on the other end of the VPN to ip address Depending on the operating system you are using there, the syntax to ifconfig may be different, just check the man page for ifconfig and you learn how to do it.

Then, this script has to be executed every time the client system connects to the server system. To do that, we can add a call to the script to the beginning of the line where our public key is stored in our ~/.ssh/authorized_keys file in the server:

tunnel="1",command="~/bin/start_vpn" ssh-rsa AAAAB3Nza ...

Here I've saved the shell script as start_vpn inside my ~/bin directory and I tell ssh to execute that command after each connection is made. I also tell SSH to use the first tun device for connections authenticating using that key (if you want more users to use the vpn you have to use more tun devices).

Setup the "client"

On the "client" machine we will need another shell script, something like this:

/sbin/ifconfig tun0 netmask
/sbin/route add -net

Similar to what we did on the server, here we tell the system to bring up a virtual device - in this case tun0 - with an assigned ip address connected to the ip address on the other end of the VPN.

Here we also tell the system to route all traffic for the network through the VPN. You can route traffic for specific hosts, more subnets or even route all traffic through the VPN.

Again, as it happens in the case of the "server", we have to tell ssh to execute this script every time we connect to the server. The easiest way to do that is adding en entry for the server in the file ~/.ssh/config in the home directory of our user in the client system.

The entry should be something like this:

Host vpnserver
 HostName vpn.example.net
 User root
 Tunnel yes
 TunnelDevice 0:1
 PermitLocalCommand yes
 LocalCommand ~/bin/start_vpn

This would allow us to connect to the vpn server by simply doing:

ssh vpnserver

It will connect to host vpn.example.net as user root and will start tunnel device forwarding between the two systems using the interfaces tun0 (local) and tun1 (remote), executing the LocalCommand upon successful connection to the remote system. That local command is our shell script that brings up the tun0 device and sets the proper routing of traffic through the VPN.

For more information about tunnel device forwarding, check the man page for ssh(8), look for the -w parameter when invoking the ssh client.

Setup ready, running the VPN

Ok, setup ready. Now we can start the vpn. In a shell in the client system run:

ssh -vf vpnserver true

Parameter -v will make the ssh client more verbose, showing information about the connection, authentication and tunnel setup. You can provide up to 3 v (like, -vvv) so it shows more information. Using -vv can be specially handy for VPNs, as it will report things like packet size adjustments while the VPN is running.

Parameter -f tells the ssh client to fork to the background before executing any command.

Finally, I pass true as the command to be executed on the server as we do not want the ssh connection to be a shell session.

Connect as root, seriously?

I left this one for the end of the post on purpose. Yeah, I'm connecting as root, which means you have to set your OpenSSH server to accept connections as the super user. Recommendation, look for the option PermitRootLogin in the sshd_config(5) man page and learn how to adjust/secure a bit root logins.

The reason for using root to start the ssh connection, and hence the VPN, is that in order to start the vpn both users in the client and server systems will need privileges to bring up the tun interfaces, as well as modify routing (on the client). Feel free to deal with this through sudo (or doas in recent OpenBSD systems) if you don't feel like doing this as root.

Bonus: bypassing proxy+firewall setups

One neat trick I've learnt some time ago is that you can use this quick OpenSSH VPNs to bypass setups where you are limited to connections to hosts on port 80, through a proxy+firewall.

I found that kind of places in my travels. You connect to the wifi network in some hotel, public library or coworking space and suddenly you notice you cannot connect to your OpenSSH servers from there, maybe you can't even connect your mail client to your imaps server. Web traffic (both http and https) work fine though.

Well, there is a solution for that (without having to mess with ptunnel, for example). Before travelling, add this rule to pf on the OpenBSD box that acts like the VPN server:

# allow connections on ports 80/443, redirecting to 22, useful to bypass
# firewalls in some places
pass in quick on $ext_if inet proto tcp from any to ($ext_if) \
     port { 80, 443 } rdr-to port 22

Et voilà, all traffic to the default http and https ports will be redirected transparently to your OpenSSH server.

If the server is running another operating system without pf support, check the docs for any packet filtering solution available to your system, or simply put the OpenSSH server listening on those ports too.

Now in your client system (your laptop, most probably) add another entry to ~/.ssh/config, something like:

Host vpnhttp
 HostName vpn.example.net
 Port 80
 User root
 Tunnel yes
 TunnelDevice 0:1
 PermitLocalCommand yes
 LocalCommand ~/bin/start_vpn

Same thing as we had already, but setting a different port. If we connect now to that server:

ssh -vvf vpnhttp true

The VPN will be created and the traffic will be tunneled through it, going out through that connection to port 80 (or 443, depending on which one you choose), bypassing the limitations of that wifi network.

Some final notes

As stated in the ssh(8) man page:

Since an SSH-based setup entails a fair amount of overhead, it may be more suited to temporary setups, such as for wireless VPNs. More permanent VPNs are better provided by tools such as ipsecctl(8) and isakmpd(8).

This means that this won't be the most efficient VPN you can use, but I think it is the easiest and quickest VPN to setup you can get.

I hope some of you will find this one useful, thanks for reading!

Posted by wu at 08:53 | Comments (0) | Trackbacks (0)