OpenVPN in a Docker container on CentOS 7 with SystemD support

September 10, 2015

In this guide, we will set up Docker on a Digital Ocean CentOS 7 droplet, then set up OpenVPN. We’ll also discuss how to create a custom SystemD service file so that we can manage our container with systemctl commands.

VPNs (virtual private networks) are a great way to secure your internet traffic over untrusted connections. It can provide easy access to your home network file server or other machines that you don’t want to expose to the internet directly. Also, you can use it to circumvent blocked sites or services on a company network, or it can be used as a proxy to access restricted content in your country (like Hulu or Netflix).

Docker allows you to easily deploy and manage Linux containers: isolated, virtualized environments. Their isolation makes them secure and easy to manage, especially for developers who can develop their container, without worrying about which distro or even OS it will be deployed to.

This guide assumes you already have a CentOS 7 server set up. I recommend using Digital Ocean, but any provider which gives you root access will work as well. This guide should also work with any distribution that uses SystemD. I liked the set up so much, that I implemented it on my home Arch server as well.

Docker

First, let’s install docker:

sudo yum -y update
sudo yum -y install docker docker-registry

Once that’s done, we’ll start Docker and then enable it to start at boot:

sudo systemctl start docker.service
sudo systemctl enable docker.service

If you don’t want to have to type sudo every time you use the docker command, then you’ll have to add your user to the group ‘docker’. Do so with:

sudo usermod -aG docker $USER
newgrp docker

The second command will make your current session aware of your new group.

VPN

Now that Docker is up and running, we’ll need to set up busybox and OpenVPN. busybox is a super minimal docker image designed for embedded systems. We just want it for its small footprint. All we’re running is a VPN, so theres no need for extra fluff.

Get it set up with:

sudo docker run --name dvpn-data -v /etc/openvpn busybox
docker run --volumes-from dvpn-data --rm kylemanna/openvpn ovpn_genconfig -u udp://$DOMAIN:1194
docker run --volumes-from dvpn-data --rm -it kylemanna/openvpn ovpn_initpki

The first command pulls the busybox image and creates a new container called ‘dvpn-data’. The second command starts a container that will hold the configuration files and certificates. Replace $DOMAIN with the IP or domain of your server. Take note that port 1194 will need to be opened in your firewall. The thrid command will generate Diffe-Hellman parameters. It will take a long time so just be patient.

To open the required port in firewalld, issue the following command:

sudo firewall-cmd --permanent --zone=public --add-port=1194/udp

Now we need to create the credentials that will allow your client to connect to the VPN.

sudo docker run --volumes-from dvpn-data --rm -it kylemanna/openvpn easyrsa build-client-full $CONNECTION_NAME nopass
sudo docker run --volumes-from dvpn-data --rm kylemanna/openvpn ovpn_getclient $CONNECTION_NAME > $CONNECTION_NAME.ovpn

Replace $CONNECTION_NAME with whatever you want to call your VPN connection. I named mine after my server name. You will be asked to create a password during the process, just pick one. It will take a while to do some crypto stuff, but eventually you’ll get an ovpn file in your current director. This is what will allow you to add the connection to your client. You will need to securely move this to the machine that will be connecting to your vpn. rsync or scp are good options. You could even use a usb thumb drive.

Since the first machine I used this VPN for was a Mac I use at work, I chose Tunnelblick for my client. After it’s installed, double clicking on the ovpn file is all the set up that was needed to add the connection to Tunnelblick on Mac. Consult your client’s documentation if this doesn’t work for you.

Manage your new container with SystemD

Now that we’ve got all of the docker stuff out of the way, let’s create a custom systemd service file so we can manage our new container with the systemctl command. SystemD service files are like init or Upstart scripts, but can be more robust and even take the place of Cron.

In CentOS 7 and Arch, these files are kept in /etc/systemd/system/ so we’ll put our’s their too. Fire up your text editor of choice, for me it’s sudo vim /etc/systemd/system/dvpn.service, and paste in the following:

[Unit]
Description=OpenVPN Docker Container
Requires=docker.service
After=docker.service

[Service]
Restart=always
ExecStart=/usr/bin/docker run --name vpn --volumes-from dvpn-data --rm -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn
ExecReload=/usr/bin/docker stop && /usr/bin/docker run --name vpn --volumes-from dvpn-data --rm -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn
ExecStop=/usr/bin/docker stop vpn

[Install]
WantedBy=local.target

There’s a lot going on here, so let’s break it down. The stuff in the [Unit] section is straigtforward enough. We give our service file an arbitrary description. The Requires=docker.service and After=docker.service mean that this service won’t start until after the docker service has started.

The Restart=always means that our service will restart if it fails. The ExecStart= tells systemd what to run when we start the service. Let’s break this command down further, to help you understand what’s going on here:

You can find more info about the docker run command in the Docker documentation, it has tons of options. Of course, you could just check the man page for it, with man docker run.

Finally, the [Install] section basically allows us to enable the service to be enabled to start at boot. You can read more about systemd service files in this excellent tutorial: Understanding Systemd Units and Unit Files.

Now that our service is created we can start it and enable it to load at boot with:

sudo systemctl start dvpn.service
sudo systemctl enable dvpn.service

You can also check its status with sudo systemctl status dvpn.service

And that’s it! You now have a SystemD managed, Docker controlled, OpenVPN set up. Enjoy!

UPDATE:I tried following my own guide to create a vpn on my home Arch Linux rig and ran into some problems. You might get iptables errors when attempting to start the dvpn service file created above.

┌─[jay@hal]─(~) 
└─[13:19]$ docker run --name vpn --volumes-from ovpn-data --rm -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn
Error response from daemon: Cannot start container d8afcbc7069b0530893779c9abf4d10aa73ab53f820c310a8baf2b956f79877c: failed to create endpoint vpn on network bridge: iptables failed: iptables --wait -t nat -A DOCKER -p udp -d 0/0 --dport 1194 -j DNAT --to-destination 172.17.0.2:1194 ! -i docker0: iptables: No chain/target/match by that name.
 (exit status 1)

It is possibly due to either the xt_conntrack kernel module not loading or because you simply need to restart the firewalld and docker daemons to reload the iptables rules. Additional info can be found here. Try restarting the daemons first with:

sudo systemctl restart firewalld
sudo systemctl restart docker

If that doesn’t work, try loading the kernel module:

sudo modprobe xt_conntrack

Tags

Secure Your WordPress With a Free SSL Certificate in Apache on CentOS 7

July 3, 2015

It is simple enough to use a self-signed certificate to encrypt traffic to your site with SSL, but if you have a WordPress blog or any site that might see lots of visitors, then a self-signed certificate is not an option: How many average users are going to proceed to your site with a warning from their Web browser about an untrusted connection? This guide will show you start to finish how to get a free SSL certificate from StartSSL, install it on your server, configure apache, and set up WordPress to use https.

All of the information I’m using is from these guides:

If you get stuck, it might help to reference one of these guides. My set up is a CentOS 7 Digital Ocean droplet with apache and WordPress, but a lot of these steps should work for other distributions. Also, keep in mind that the free certificate offered by StartSSL is for non-commercial use only.

What you’ll need

StartSSL

Open up Chrome and head to startssl.com. Click on “Express Signup,” fill out the forms and hit continue. Check your email for the verification code. Click the link in the e-mail and you will be asked to generate a private key. Choose “High” for the grade. Once it’s done, click “Install” and Chrome will present you with a pop-up that says it has been successfully installed.

This is not your SSL certificate, it’s just a key that you will use to log in to the StartSSL Web site. Click on “Control Panel” and then “Authenticate.” Chrome will give you a pop-up to authenticate with the site.

Validate your domain

Once you’re in the Control Panel, click on the Validations Wizard tab and select “Domain Name Validation” from the drop-down menu. Choose whichever e-mail you have access to (like postmaster@yourdomain.com).

If you’re using Google Apps for your e-mail provider, you can just create a group called webmaster and give it public access permissions to post to the group. Add yourself to the group and you will get any messages sent to webmaster@yourdomain.com. This is any easy way to get extra addresses forwarding to your main Google Apps account without creating another user.

Check that whatever account you’re using for the validation e-mail and paste in the code.

Create the Certificate

In the Control Panel, click on the “Certificates Wizard” tab. Select “Web Server SSL/TLS Certificate” from the drop-down menu. Hit continue and enter a strong password. You’ll get a text box that contains your key. Copy its contents into your text editor of choice and save the file as ssl.key.

Hit continue and select your recently verified domain. Choose a sub-domain on the next screen. You probably want to pick ‘www’, but it’s up to you. Hit continue and you’ll get another text box, this time containing your certificate. Copy it to your text editor and save it as ssl.crt.

Download the CAs

Click on “Toolbox,” and download the StartCom Root CA and the StartSSL’s Class 1 Intermediate Server CA. Just right-click on the links with those names and hit save as.

Now we need to unencrypt your your private key so that your sever can use it. Do so with:

openssl rsa -in ssl.key -out private.key

You should now have 5 files:

ca.pem
private.key
sub.class1.server.ca.pem
ssl.crt
ssl.key

Note: the private.key file is the unencrypted version of your private key. Make certain that no one has access to it and that you delete it from your local machine once you upload it to the server. It isn’t necessary to upload the ssl.key file to your server. Let’s upload the ones we do need though, using scp:

scp -p 2222 {ca.pem,private.key,sub.class1.server.ca.pem,ssl.crt} user@yourserver.com:/home/user/

In this example, the ssh listening port is 2222, change it to whatever your port is. You can also specify a different destination directory by changing /home/user to whatever you want.

Apache

SSH into your server and let’s get it set up.

$ sudo yum install -y mod_ssl
$ sudo mkdir /etc/httpd/ssl
$ sudo mv {ca.pem,private.key,sub.class1.server.ca.pem,ssl.crt} /etc/httpd/ssl
$ sudo nano /etc/httpd/conf.d/ssl.conf

The first command will install the ssl module for apache, the second creates a directory for your certificate to live in. The third command will move all of your certificate files to your newly created ssl directory. The last will open up the ssl configuration file for apache. Look for this line:

<VirtualHost _default_:443>

Uncomment (delete the # at the beginning of the line) the DocumentRoot and ServerName lines and change example.com:443 to whatever your domain is. It is important that this match what you entered when you created the certificate.

Uncomment these lines as well and change the location of the files to match what’s shown here:

SSLCertificateFile /etc/apache2/ssl/ssl.crt                           
SSLCertificateKeyFile /etc/apache2/ssl/private.key                        
SSLCertificateChainFile /etc/apache2/ssl/sub.class1.server.ca.pem

Once you’re done, save and close the ssl.conf file and open up your site’s configuration file:

$ sudo vim /etc/httpd/sites-enabled/yoursitesname.com.conf

And add these lines before the closing :

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

This will force https for the whole site, so that even if users don’t type out https:// before your address, they will still be protected.

Restart the apache server:

$ sudo systemctl restart httpd

Test that it works by going to https://yourdomain.com. You should see a little lock in the address bar. If you get an Untrusted Connection error, then you probably forgot to change the location of the certificate files from the defaults in the ssl.conf file. If you get a lock symbol, but with a triangular alert symbol, then you’ve got yourself a mixed content warning. No big deal, we’ll fix that in the next step.

WordPress

Log in to your WordPress admin portal and click on “Settings,” and change the “WordPress Address (URL)” from http://yourdomain.com to https://yourdomain.com. Make the same change to the “Site Address (URL)” field as well.

If you’ve got the Mixed Content warning, then you’ve got some work to do. This warning basically means that your Web browser has detected some content on the page that is being fetched with plain old http, meaning it’s not encrypted and secure. This could mean anything, but images you’ve added to posts is a great place to start. Take a look at one of your posts with images and view it in text mode. Scroll down to where your image is and check the html, if it looks like this:<img src="http://yourdomain.com/cat.jpg" ... then that’s probably the problem.

There are a number of ways to fix this. If you have a new site, then you can just click through your posts and add an ‘s’ after http to all of your image tags. If you have hundreds or more images, this could get tedious. This guide: Moving to HTTPS on WordPress has some SQL kung fu that might be able to automate the process for you. <iframe> or <link> tags could also be causing the problem if they are calling http. This stackoverflow post has some more info as well.

Keep an eye out for mixed content warnings on other pages, but otherwise you should be done!

Tags

WordPress Automated Backup Script with rsync and systemd

April 16, 2015

Need to back up your WordPress site automatically? Of course you do! There are loads of ways to do this, but I settled on a solution that is simple, reliable and automated.

I modified a simple script I found here and used systemd to create a daily timer and service unit to execute the script daily, and then used systemd on my local machine to mirror the backups with an rsync one-liner script. Let’s get started! Note: This guide assumes you have ssh access to the server where your WordPress is hosted. If you don’t then this guide is not for you.

The Backup Script

First, let’s take a look at the script:

#!/bin/bash

# taken from http://theme.fm/2011/06/a-shell-script-for-a-complete-wordpress-backup-4/
# a simple script to back up your wordpress installation and mysql database
# it then merges the two into a tar and compresses it

TIME=$(date +"%Y-%m-%d-%H%M")
FILE="example.com$TIME.tar"
BACKUP_DIR="/home/user/Backups"
WWW_DIR="/var/www/html"

DB_USER="user"
DB_PASS="password"
DB_NAME="wpress"
DB_FILE="example.com.$TIME.sql"

WWW_TRANSFORM='s,^var/www/html,www,'
DB_TRANSFORM='s,^home/user/Backups,database,'

tar -cvf $BACKUP_DIR/$FILE --transform $WWW_TRANSFORM $WWW_DIR

mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/$DB_FILE

tar --append --file=$BACKUP_DIR/$FILE --transform $DB_TRANSFORM $BACKUP_DIR/$DB_FILE

rm $BACKUP_DIR/$DB_FILE

gzip -9 $BACKUP_DIR/$FILE

exit 0

There’s a lot going on here and I won’t take the time to explain every bit of it, but basically, this script makes a tar of your /var/www/html directory (where WordPress usually lives), dumps your mysql database (where your posts are stored), appends that to the tar of your /var/www/html directory so it’s one file, adds today’s date to it, and then compresses it. If you want a more detailed explanation, read the original guide to this script here.

SSH to the server where your WordPress lives and open up your text editor of choice, I’ll use nano for this guide. First though, let’s make a place to keep your scripts:

$ mkdir ~/Scripts
$ mkdir ~/Backups
$ cd Scripts
$ nano wp.backup.sh

Paste in the the script and edit it to suit your site. Once you’re done, hit ctrl+x to save. Now we need to make the script executable. Test it, with the second command. The ./ means the current directory. Unless an executable file is in /usr/bin/ or a similar directory for your distro, then you need to specify the full path of the file to be executed. You could also type out the full path to the script, but since we’re already in the directory, we’ll use the shortcut.

$ chmod +x wp.backup.sh
$ ./wp.backup.sh

If it worked, your terminal will be flooded with all of the folder names that tar just archived. To verify, let’s move to the Backups directory and see what our script made for us:

$ cd ~/Backups
$ ls
example.com.2015-04-16-1112.tar.gz

To open up your archive, just use tar -xvf. The database directory will contain your mysql dump and the www directory contains the contents of your /var/www/html directory.

$ tar -xvf example.com.2015-04-16-1112.tar.gz
$ ls
example.com.2015-04-16-1112.tar.gz
database
www

To Cron or Not to Cron

Cron represents the easiest way to automate this script for daily backups. For example, you could just do:

$ crontab -e
@daily /home/user/Scripts/wp.backup.sh

And cron would run your script every day at midnight.

Cron is great, but since my WordPress lives on a CentOS 7 server and my local machine runs Arch (which doesn’t even come with cron by default), I wanted to use systemd to automate my script. Not only is it an opportunity to learn more about systemd, it will make things easier in the future if I want to exapnd on this simple script and backup solution.

First, let’s make a .timer file. .timer files work sort of like crontab entries. You can set them up for the familiar daily, hourly, or weekly timers or you can get super specific with it. Read the wiki for more info.

$ sudo nano /etc/systemd/system/daily.timer
[Unit]
Description=Daily Timer

[Timer]
OnCalendar=daily
Unit=wp.backup.service

[Install]
WantedBy=multi-user.target

The key part here is the OnCalendar=daily. Timers can be set to all sorts of values here, but we’ll be using daily. You could set yours to weekly or even monthly if you like. The other thing to take not of here is Unit=wp.backup.service. That’s the service file that will call our back-up script. When you’re done editing, hit ctrl+x to save. Let’s make that service file now:

$sudo nano /etc/systemd/system/wp.backup.service
[Unit]
Description=Wordpress Backup Script

[Service]
Type=simple
ExecStart=/home/user/Scripts/wp.backup.sh

[Install]
WantedBy=daily.timer

Change the ExecStart=/home/user/Scripts/wp.backup.sh to wherever you saved your script and hit ctrl+x to save. To test that it works, type:

$ sudo systemctl start daily.timer
$ sudo systemctl start wp.backup.service

Now let’s check the status of our new timer and service:

$ sudo systemctl status daily.timer && sudo systemctl status wp.backup.service

Should return something like this:

daily.timer - Daily Timer
   Loaded: loaded (/etc/systemd/system/daily.timer; enabled)
   Active: active (waiting) since Thu 2015-04-16 15:06:44 EDT; 33min ago

Apr 16 15:06:44 example systemd[1]: Starting Daily Timer
Apr 16 15:06:44 example systemd[1]: Started Daily Timer.
wp.backup.service - WordPress Backup Script
   Loaded: loaded (/etc/systemd/system/wp.backup.service; enabled)
   Active: inactive (dead) since Thu 2015-04-16 15:06:54 EDT; 33min ago
  Process: 332 ExecStart=/home/user/Scripts/wp.backup.sh (code=exited, status=0/SUCCESS)
 Main PID: 332 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/wp.backup.service

Apr 16 15:06:46 example wp.backup.sh[332]: /var/www/html/wp-content/plugins/jetpack/_inc/lib/
Apr 16 15:06:46 example wp.backup.sh[332]: /var/www/html/wp-content/plugins/jetpack/_inc/lib/markdown/
Hint: Some lines were ellipsized, use -l to show in full.

So why have two separate systemd files for what seems like one job? Well with this set up, it’s easier to add more complexity later if we need to. You could add other options for when backups should occur. You could add other scipts to be executed daily. Plus, you can call your backup script with sudo systemctl start wp.backup.service and check its status with sudo systemctl status wp.backup.service.

So now that we have our server making automatic daily backups, let’s backup the backups to an offsite location. If the server goes down, or your hosting company goes out of business, then you’ll be out of luck if your only backups live on the server that you can no longer access.

rsync

Rsync is one of my favorite linux tools. It can sync files and directories over ssh and just sync the differences in the files, making it perfect for our needs. I have an Arch linux home server that will function as the offsite backup location, but you could use another distribution. You can even use rsync on OSX, but that’s another guide.

If you haven’t done so already, you’ll need to set up ssh keys for your server and local machine. The Arch wiki page on ssh keys should have all of the info you need. There are also a plethora of guides on the interwebs that can help.

Once your ssh keys are set up, let’s set up an rsync command to pull the backup archives from the server and mirror them to our local machine:

rsync -avzP -e "ssh -p8888" user@example.com:/home/user/Backups/* /home/user/Backups/

Let’s break this down. -avzP tells rysnc to archive, use verbose mode, compress, and print the progress to the terminal, respectively. The -e "ssh -p8888" tells it that the remote server uses port 8888 for ssh. The user@example.com:/home/user/Backups/* uses the wildcard “*” to grab any files in the Backups directory on the remote server. And the last bit tells rysnc where to put the files.

Since we’re going to be using this command in a script that gets executed by systemd, we need to take into account that it will be executed as root. This can cause problems as ssh will look in the /root/.ssh directory to find the private key file and won’t find it, causing the command to fail. To fix this, let’s add an entry to our /etc/ssh_config file to define our server and tell ssh where to find the private key for it.

$ sudo nano /etc/ssh/ssh_config
Host example
    HostName example.com
    Port 8888
    User user
    IdentityFile /home/user/.ssh/id_rsa

Add the above lines to your ssh_config and substitute your information. Ctrl+x to save. Now you can ssh to your server with just ssh example. You could also do rsync -avzP example:/source/files /destination/directory.

Now that we’ve got that sorted, let’s make it a script:

$ nano ~/Scripts/wp.offsite.backup.sh
#!/bin/bash
# one-liner to backup wordpress files from jay-baker.com

rsync -avzP example:/home/user/Backups/* /home/user/Backups/

Ctrl+x to save. Don’t forget to make it executable with chmod +x wp.offsite.backup.sh, and then test it with ./wp.offsite.backup.sh. Now that we’ve got the script working, let’s set up our systemd timer and service files. These will be the same as the ones on the server, so just cut and paste those, being careful to change the name of the script. You could also change your timer to weekly if you like.

Once you’ve got them set up the way you want, then test and enable them with:

$ sudo systemctl start daily.timer
$ sudo systemctl start wp.offsite.backup.service
$ sudo systemctl enable daily.timer
$ sudo systemctl enable wp.offsite.backup.service

And that’s it! We now have automatic, daily backups of our WordPress installation and mysql database which are then mirrored to another machine with rsync: automatically and daily. From here, we could write further scripts to make monthly archives and move them to external storage or just remove any backups older than a month.

Tags