Linux Containers: Part 5, Creating Your Own VPN

E

Eric Hansen

Guest
Part 4 we covered how to set up our container to reach the outside world (a.k.a.: WAN/Internet). This now allows us to update our container, install software and even give us the chance to make the container a VPN!

Most people will set up a VPN by purchasing a VPS and installing it that way. While that is easier in a few ways, its not very resourceful. If your host server (that houses the containers) uses little bandwidth (for example, before doing this VPS mine was using ~450 MB a month out of 600 GB), making the container a VPN server will save you money in the end so you don’t have to buy a server that you’ll barely ever touch.

Here’s what you’ll need to install in the container to start:
  • openvpn - This acts as both a server and client, but we’ll configure it as a server and show you how to set up a client as well
  • openssl - If this isn’t already installed, do it now, we will need it
  • iptables - You’ll see why
  • iptables-persistent - Helper scripts to auto-load rules (hit “Yes” to both dialogs that pop up)
The rest of the guide assumes you’re consoled and logged in as root in the container. If you’re not, do that now.

Setting Up OpenVPN
There’s a few ways to set up a VPN, I prefer OpenVPN simply because it's nicer in my opinion. To start we need to set up some variables. Thankfully the hard part is already done for us when we install the OpenVPN package.

First we will create the directory we need:
Code:
mkdir -p /etc/openvpn/easy-rsa
cp -r /usr/share/doc/openvpn/examples/easy-rsa/2.0/* /etc/openvpn/easy-rsa/
Now go to the new directory:
Code:
cd /etc/openvpn/easy-rsa
Edit the vars file, changing these settings:
Code:
export KEY_COUNTRY="US"
export KEY_PROVINCE="MI"
export KEY_CITY="Big Old City Name"
export KEY_ORG="Some company name or something"
export KEY_EMAIL="[email protected]"
I did this for my new business so I’ll show you what mine looks like:
Code:
export KEY_COUNTRY="US"
export KEY_PROVINCE="MI"
export KEY_CITY="Melvindale"
export KEY_ORG="Anzen Solutions, LLC"
export KEY_EMAIL="[email protected]"
Creating the Certificate Authority
After that, we’ll make sure our shell session can read the variables, then we’ll start fresh and build a certificate authority (CA) file:
Code:
source vars
./clean-all
./build-ca
The CA file will be used to essentially authenticate all the certificates (similar to the CA files used when you purchase an SSL file). This helps ensure that no one is sending the OpenVPN server a forged certificate and that yours has not been tampered with.

Generate A Server Certificate
Both our server and every client will need a certificate to authenticate with each other. We’ll first create one for the server by running this command:
Code:
./build-key-server server
This is like creating a self-signed SSL certificate, but all the variables will be populated with what we entered. The most important aspect is to make sure that the “Common Name” variable is unique (no two certificates can have the same CN field value).

You’ll be asked “Sign the certificate?” then “1 out of 1 certificate requests certified. commit?”, answer “y” to both.

Next we will build the Diffie-Hellman key exchange information:

Code:
./build-dh
All of these keys we have (and will) generated reside in /etc/openvpn/easy-rsa/keys/. Lets copy the ones we need for the server over to the main directory:
Code:
cd keys
cp server.{crt,key} ca.crt dh1024.pem /etc/openvpn/
cd ../..
If you ran ./build-key-server with a different parameter than “server”, replace the “server” portion in the above line as well.

Configure The Server
Luckily OpenVPN provides us with premade examples that work pretty well out of the box with just some minor tweaks. But we have to extract it first:
Code:
cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz /etc/openvpn
gzip -d server.conf.gz
I don’t advise deleting the server.conf.gz file until you know for sure your VPN works.

Open up the server.conf file in your favorite editor (again, doesn’t matter what you use). Here are the things you might or will need to pay attention to:
  • proto udp
    • We will use UDP because it causes less overhead while sacrificing higher chance of data loss
  • port 1194
    • Standard OpenVPN port, guide will assume you stick with the normal port
  • dev tun
    • Essentially tap is for bridging OpenVPN with eth0, tun is for routing traffic. There’s pros and cons to both, we’ll go with routing as its less difficult
  • ca ca.crt
  • cert server.crt
  • key server.key
  • dh dh1024.pem
    • These are what we copied over in the last section, they reside in /etc/openvpn/ so the config file will find them just fine when its read
  • server 10.8.0.0 255.255.255.0
    • This is where you specify your VPN’s network. This 255-allocation block is fine for our needs, tweak if you need something better
  • ifconfig-pool-persist ipp.txt
    • Stores a record of all connected devices. In the event OpenVPN has to restart, any connections stored will be restarted as well preserving IP and such.
  • push “redirect-gateway def1”
    • This one confuses me and you have to manually add this one in. But this will let any VPN clients reach the outside network (along with an iptables rule specified later on)
  • push "dhcp-option DNS 208.67.222.222"
  • push "dhcp-option DNS 208.67.220.220"
    • This tells the VPN client to use these DNS servers (OpenDNS provided by default). Change according to what you wish they to be.
  • client-to-client
    • Allows VPN clients to communicate with each other. I enabled this but really should be disabled unless you need Bob and Alice to talk to each other when they are VPN’ed
  • tls-auth ta.key 0
    • Creates sort of a 2nd layer of defense against certain attacks by also requiring this server-generated key
  • comp-lzo
    • Compress traffic to save a bit on bandwidth
  • persist-key
  • persist-tun
    • If OpenVPN is restarted it won’t try to access stale data
  • status openvpn-status.log
    • Creates a file oepnvpn-status.log in /etc/openvpn/ that is overwritten every minute detailing OpenVPN statistics. Could also be disabled but we’ll enable it for now
  • log openvpn.log
    • Similar to status above but standard log file. There’s also log-append that does the same but just appends data to the file. log will overwrite it every time OpenVPN is started.
  • cipher
    • Here you can specify which one you want. We’ll stick with the default (Blowfish) so no need to uncomment anything.
We set the “tls-auth” option, but we never created a ta.key file. Lets do that now:
Code:
openvpn --genkey --secret ta.key
Also as mentioned above we will need to create an iptables rule so that we can successfully reach the outside. If you remember in part 4 we had to create a MASQUERADE rule on our host/router so that it can send our traffic out through the Internet adapter. The same rule applies here as well. Just we need to make a slight adjustment to the rule:
Code:
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
This will send all of our VPN traffic (assuming you used the default 10.8.0.0/24 server setting) to eth0 and ship it out to the Internet. The only change from the last part is that the source IP is different.

Some guides you’ll see also state to create a rule for the tun or tap device, but I’ve not had any issues with that. Now, if you set the default policy to something other than ACCEPT or something, then yes, create one. But that’s out of the scope of this tutorial.

Running OpenVPN Server
With our server configured properly, lets see it start up!
Code:
service openvpn start

If the above command doesn’t run, try this:
Code:
/etc/init.d/openvpn start

You can then check netstat:
Code:
root@linux_org:/etc/openvpn# netstat -ntlup
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address  Foreign Address  State  PID/Program name
tcp  0  0 0.0.0.0:22  0.0.0.0:*  LISTEN  488/sshd
tcp6  0  0 :::22  :::*  LISTEN  488/sshd
udp  0  0 0.0.0.0:1194  0.0.0.0:*  772/openvpn
udp  0  0 0.0.0.0:47575  0.0.0.0:*  466/dhclient
udp  0  0 0.0.0.0:68  0.0.0.0:*  466/dhclient
udp6  0  0 :::3199  :::*  466/dhclient
Notice we have 0.0.0.0:1194 listening with protocol UDP? Our VPN works!

Creating Our First Client
So now that we have this all set, lets create our first client identity in our new world:
Code:
cd easy-rsa
source vars
./build-key client_name
Note that “client_name” will also populate the “Common Name” field, and as I said everyone connected to the VPN needs a unique CN (Common Name). This is the same as generating a server certificate so I won’t go over much else.

I’ll assume you have the OpenVPN program also installed on a remote machine (say, your desktop). Even if you did all of these parts on the same machine though it’ll be fine as long as you can ping the container’s IP (10.0.3.42 in this case). Another assumption: I’ll figure you can.

From the container you will need the following files:
  • /etc/openvpn/ta.key
  • /etc/openvpn/ca.crt
  • /etc/openvpn/easy-rsa/keys/client_name.crt
  • /etc/openvpn/easy-rsa/keys/client_name.key
The ta.key and ca.crt are used to validate and authenticate any future requests (think of the ta.key file as sort of a two-factor authentication mechanism). The “client_name.crt” and “client_name.key” files might be named different, depending on what you passed to “./build-key”. I’ll figure you used “client_name”.

I thought I would be nice and provide a client configuration file that works pretty well out of the box here. Just put it in the same folder as where the above files are located, saving it as “client.conf”.

Accepting VPN Connections
Before we can connect to the server we need to make sure the host/router can accept the connection. If you plan on switching to TCP then make the appropriate change but you will have to exit out of the LXC console and add another iptables rule:
Code:
iptables -t nat -A POSTROUTING -p udp --dport 1194 -j DNAT --to 10.0.3.42:1194
Now whenever someone connects to the server itself on UDP/1194 they will be re-routed to the VPN container/server and it will do the rest.

Connecting to the Server
This part is easy. With the server running in the container, we’ll start the client in the same way somewhat. cd to the folder where the config file and keys are located and run:
Code:
sudo openvpn client.conf

This requires sudo as it will create a network device.

An easy to test this is by running the command:
Code:
curl http://checkip.dyndns.com

It should show you the IP of the host/router housing the container (its harder to prove it works if you’re running it on the same machine).

You officially have a free VPN that you can use to provide additional security when using public WiFi or wanting to access your container(s) remotely!
 

Attachments

  • slide.jpg
    slide.jpg
    26.6 KB · Views: 70,957
Last edited:


You have a typo:
iptables -t nat -A POSTROUTING -p udp --dport 1149 -j DNAT --to 10.0.3.42:1194

--dport 1194 I guess :)

Nice simplified article though... just to the point!
 
You have a typo:
iptables -t nat -A POSTROUTING -p udp --dport 1149 -j DNAT --to 10.0.3.42:1194

--dport 1194 I guess :)

Nice simplified article though... just to the point!
Nice catch, thanks :) Edited with the proper port. Sometimes that slips by me when I've already wrote 3 pages of articles hah.
 
I just finished setting up a similar scenario and I actually tend to change the default ports on remote services... not that it add extra security but at leasts keeps away automated port scanners, so I actually read the rule and it was easy to notice... but this happens to me to, and I believe any sys admin so no worries!

I was actually searching for help on another matter related to this....
I want to block traffic from openvpn interface to specific services inside the container.... but I'm more used to jails! This would also be done on the host! but it seems this is not the case with LXC right?

At least according to tcpdump... it sees no traffic inside the conatiner only... So my guess is to do what I want I have to setup iptables rules inside the container it self, not the host, or am I missing something!?
 
I want to block traffic from openvpn interface to specific services inside the container....
To make a long story short, this isn't possible, at least not when you're running both on the same machine that has 1 IP.

I wanted to do something similar: have OpenVPN and a web server set up on the same machine (1 IP) and make it so the web server only served traffic from the OpenVPN instance. I don't remember the mechanics of it but OpenVPN wouldn't play nicely with this desire (had to do with not being able to limit certain traffic). Only way this would be possible is having OpenVPN listen on 1 IP and the web server listen on another.

I'm more used to jails!
Truthfully LXC is really nothing more than a more advanced jail.

As for setting up rules, it would depend. I wrote a part 7 to this guide (not sure if its been made public yet or not though) that details how to set up a web server in a container effectively (iptables is a BAD idea for this in my opinion).

If I didn't answer your question properly let me know. But from personal experience if you have multiple services running on the same IP/adapter, its not going to allow you to segregate requests and do Houdini administration tricks, lol. Its a flaw in networking itself and how it functions.
 
,Why do you say iptables is a bad idea?

Anyway what I was trying is indeed possible!

The rules in the host simple nat the traffic to the container.... the dnat rule will redirect 1194 as you explained... and after that the host doesn't care about any filtering inside the container! Like say traffic between eth0 and lo0.... Or in this case tun0<->eth0

That filtering needs to be done inside the container, and I've used iptables for this! For example I only wanted the vpn client to access postgres on this container and nothing else.

something like this:
Chain INPUT (policy ACCEPT 1 packets, 97 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT tcp -- tun0 any anywhere lxc_eth0_ip tcp dpt:postgresql
0 0 DROP all -- tun0 any anywhere anywhere

More rules could be added ofc, but the ufw rules in the host already do all the blocking from outside traffic and container to container, all I really needed to limit was the vpn clients access!
 
Hi Eric,

Nice tutorial, it really helped alot. I followed most of it and created my openvpn on udp, but I would like to change it to tcp now. How do I go about doing it? I tried changing server.conf.gz but I still see the following line when I do a netstat -ntlup:

udp 0 0 0.0.0.0:1194 0.0.0.0:* 1257/openvpn

Could you advice me on this?

Thanks!
 


Top