ssl/tls


Nginx, Let's Encrypt, and Docker Compose

DEPRECATED!

As of 2017-7-31, this no longer suits my purpose. Here’s my updated docker-compose version 3 approach.

Introduction

I’ve used StartSSL for years. Then I discovered Let’s Encrypt. All my old StartSSL certificates are expiring, so I needed to work out a process by which I could swap them out for Let’s Encrypt certs.

jwilder’s excellent nginx-proxy has been my go-to for easy certificate configuration for some time now. I was relieved to learn that I am still be able to leverage this tool with the help of the docker-letsencrypt-nginx-proxy-companion container.

This document outlines the process by which Let’s Encrypt certificates are managed for a single nginx container behind an nginx-proxy accompanied by the docker-letsencrypt-nginx-proxy-companion. docker-compose is used to manage the overall configuration. It was proven on Ubuntu 16.04. Naturally, it is assumed that Docker and Compose are already installed. Copying and pasting the commands provided should lead to a successful deployment.

My Site

nginx-proxy proxies multiple site. I’m only serving one with nginx. I like to put all my individual Docker compositions in their own directories:

1
mkdir mysite && cd mysite

Optional

The following assumes you have some sort of site you want to serve up from the mysite/ directory. If not, just create a simple Hello, world! HTML page. Copy and paste the following to index.html:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello, world!</title>
</head>
<body>
Hello, world!
</body>
</html>

docker-compose

This configures docker-compose to serve up your site with nginx. Copy and paste the following to a file called docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
# docker-compose.yml
nginx:
image: nginx
restart: always
environment:
- VIRTUAL_HOST=example.com
- LETSENCRYPT_HOST=site.example.com
- LETSENCRYPT_EMAIL=email@example.com
volumes:
- ./:/usr/share/nginx/html

This will serve up files from the current directory (i.e., the same one that contains the new index.html page, if created).

Start docker-compose:

1
docker-compose up -d

The site won’t be accessible yet. That comes next.

nginx-proxy

As before, put the nginx-proxy Docker compositions in its own directory:

1
2
cd ..
mkdir nginx-proxy && cd nginx-proxy

Create a directory in which to store the Let’s Encrypt certificates:

1
mkdir certs

Copy and paste the following to a file called docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# docker-compose.yml
nginx-proxy:
image: jwilder/nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./current/public:/usr/share/nginx/html
- ./certs:/etc/nginx/certs:ro
- /etc/nginx/vhost.d
- /usr/share/nginx/html
- /var/run/docker.sock:/tmp/docker.sock:ro
letsencrypt:
image: jrcs/letsencrypt-nginx-proxy-companion
restart: always
volumes:
- ./certs:/etc/nginx/certs:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes_from:
- nginx-proxy

This allows nginx-proxy to combine forces with docker-letsencrypt-nginx-proxy-companion, all in one docker-compose file.

Start docker-compose:

1
docker-compose up -d

If all is well, you should be able to access your site at the address configured.


iRedMail setup and GoDaddy DNS records

I had it in mind to Dockerize email services on an Ubuntu server. I quickly realized email is a gongshow and opted for the fastest, easiest solution. This turned out to be iRedMail, which still proved a bit tricky when it came time to set up my GoDaddy DNS records.

Here’s what I did…

The system

  • Ubuntu 14.04 server
  • 1 vCPU
  • 2 GB (as recommended here)
  • 20 GB of storage

I buy my VMs from cloudatcost.com. They’re reasonably reliable and reasonably priced.

A (Host) records

Once your machine (wherever it be) is online, set the DNS A (Host) records right away. My DNS stuff is all managed at GoDaddy.

[First host record]

Then create another A record and point it to the mail subdomain:

[mail subdomain host record]

Prepare the environment

CloudAtCost sets creates a root user and sets the password. I ssh in and change it right away:

1
2
ssh root@rockyvalley.ca
passwd

There may be a compelling reason to create a non-root user, but since the iRedMail will be installed entirely as root, I’m going to skip that step until advised to do otherwise.

Set the domain name

First,

1
vim /etc/hostname

Change whatever’s inside to:

1
mail

and save. Then,

1
vim /etc/hosts

Change it to look like this:

1
2
3
4
5
6
127.0.0.1 mail.rockyvalley.ca mail localhost localhost.localdomain
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Change your domain name wherever appropriate (my example domain is rockyvalley.ca.

Reboot the machine.

Log back in:

1
ssh root@rockyvalley.ca

Execute

1
hostname -f

If you see something similar to

1
mail.rockyvalley.ca

then your server has been named appropriately.

Install iRedMail

Download the latest package.

1
2
3
4
cd /root
wget https://bitbucket.org/zhb/iredmail/downloads/iRedMail-0.9.2.tar.bz2
tar xjf iRedMail-0.9.2.tar.bz2
cd iRedMail-0.9.2

Execute the install script:

1
bash iRedMail.sh

This will install a bunch of stuff and then guide you through configuration. Press Enter to proceed past the intro screen.

Default mail storage path

[Default mail storage path]

Preferred web server

[Preferred web server]

Choose preferred backend used to store mail accounts

Use the space bar to select the database (here, PostgreSQL).

[Choose preferred backend used to store mail accounts]

Password for PostgreSQL administrator: postgres

[Password for PostgreSQL administrator: postgres]

Your first virtual domain

[Your first virtual domain]

Password for the administrator of your domain

[Password for the administrator of your domain]

Optional components

[Optional components]

Proceed with installation

[Proceed with installation]

I answered yes when asked:

1
2
< Question > Would you like to use firewall rules provided by iRedMail?
< Question > File: /etc/default/iptables, with SSHD port: 22. [Y|n]y

I answered no when asked:

1
< Question > Restart firewall now (with SSHD port 22)? [y|N]n

I figured it unwise to restart because I’m logged in to my server via ssh.

Upon sucessful completion, the installer will spit out some valuable information:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
********************************************************************
* URLs of installed web applications:
*
* - Webmail:
* o Roundcube webmail: httpS://mail.rockyvalley.ca/mail/
*
* - Web admin panel (iRedAdmin): httpS://mail.rockyvalley.ca/iredadmin/
*
* You can login to above links with same credential:
*
* o Username: postmaster@rockyvalley.ca
* o Password: somesecretpassword
*
*
********************************************************************
* Congratulations, mail server setup completed successfully. Please
* read below file for more information:
*
* - /root/iRedMail-0.9.2/iRedMail.tips
*
* And it's sent to your mail account postmaster@rockyvalley.ca.
*
********************* WARNING **************************************
*
* Rebooting your system is required to enable mail services.
*
********************************************************************

Reboot now.

Set up DNS records

MX

The A records have already been set up. Create an MX record (I’m using GoDaddy, so I deleted the existing records before proceeding):

[MX record]

SPF

This gets set as a TXT record at GoDaddy:

[SPF record]

DKIM

Log back into your server:

1
ssh root@rockyvalley.ca

Execute the following to determine your DKIM keys:

1
amavisd-new showkeys

This will return something like this:

1
2
3
4
5
6
7
8
9
10
; key#1, domain rockyvalley.ca, /var/lib/dkim/rockyvalley.ca.pem
dkim._domainkey.rockyvalley.ca. 3600 TXT (
"v=DKIM1; p="
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApTNgVVL2+vIIcq9xioc5"
"B/ydJxaQRZ1eBKkO7mhz2ir5k3DdWl+y65GYR8TbP3z3essbwOnPocqnwX81RoW1"
"VAhPYlHU57OLSXnk3qYcRDHpT/UU/dOGdFclpuAXazUg0l8QhTgadtxsIRDlckKg"
"Vr6II7knZUrhfm84uJ3w858OIrzy8KOSXXfc8npTn48iy4okJGbHvVxE05m6f9/g"
"ie63Z5XkIZeJu7Nj6O/IOVitZh3uiKoOlBHULKqpNtHtPrnZHHX51OLkiezUBvG+"
"slHGPK710iW5ITDy5qm/VaANigXBnPrdF3S3sZMFprwa9GhGSkrnnJ40eCJVFgCm"
"FQIDAQAB")

All the stuff between the brackets needs to be put onto one line, like this:

1
v=DKIM1; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApTNgVVL2+vIIcq9xioc5B/ydJxaQRZ1eBKkO7mhz2ir5k3DdWl+y65GYR8TbP3z3essbwOnPocqnwX81RoW1VAhPYlHU57OLSXnk3qYcRDHpT/UU/dOGdFclpuAXazUg0l8QhTgadtxsIRDlckKgVr6II7knZUrhfm84uJ3w858OIrzy8KOSXXfc8npTn48iy4okJGbHvVxE05m6f9/gie63Z5XkIZeJu7Nj6O/IOVitZh3uiKoOlBHULKqpNtHtPrnZHHX51OLkiezUBvG+slHGPK710iW5ITDy5qm/VaANigXBnPrdF3S3sZMFprwa9GhGSkrnnJ40eCJVFgCmFQIDAQAB

All this gets set as another TXT record:

[DKIM record]

This may take some time to propagate (a couple hours even). These commands will help confirm that everything is set up okay:

1
2
dig -t txt dkim._domainkey.rockyvalley.ca
nslookup -type=txt dkim._domainkey.rockyvalley.ca

You’ll see the DKIM TXT record you just set once everything has propagated.

Verify public key availability:

1
amavisd-new testkeys

You should see this, if successful:

1
TESTING#1: dkim._domainkey.rockyvalley.ca => pass

SSL/TLS

At this point, assuming time allowed for propagation, you should be able to send and receive email from the postmaster account. However, the certificates iRedMail sets up for you are self-signed, which means you get an ugly warning whenever you try to access your webmail. To fix this, you’ll need to get certs from a trusted certificate authority. I like to use startssl.com because they’re free.

Once obtained, transfer the certificates to the mail server:

1
scp rockyvalley.ca.tar.gz root@rockyvalley.ca:~

Login,

1
ssh root@rockyvalley.ca

unzip, decrypt, and lockdown:

1
2
3
4
tar -zxvf rockyvalley.ca.tar.gz
cd rockyvalley.ca
openssl rsa -in ssl.key -out iRedMail.key
chmod 400 iRedMail.key

Since I chose Nginx as my web server and StartSSL as my CA, I need to chain my ssl.crt with StartSSL’s intermediate certificate:

1
cat ssl.crt sub.class1.server.ca.pem > iRedMail.crt

The certificates are now ready to be put in place. The self-signed certificates are stored in:

  • /etc/ssl/certs/iRedMail.crt
  • /etc/ssl/private/iRedMail.key

The new certificates were already named appropriately during decryption and chaining, so now it is simply a matter of overwriting the existing self-signed certificates:

Copy the certs to the correct directories:

1
2
mv iRedMail.crt /etc/ssl/certs/
mv iRedMail.key /etc/ssl/private/

Reboot the machine.

I rebooted in lieu of restarting individual services. Once back online, test sending and receiving. Everything should be good to go.


Self-signing security certificates

Obtaining or self-signing security certificates is a frequent step in my notes. The intent of this post is to DRY out my blog.

To self-sign a certificate, first create a certs/ directory:

1
2
mkdir certs
cd certs

In the following command, note the keyout and out options. I like to name my certificates in accordance with my production site’s URL and subdomain (if any). For example, suppose I need a certificate for example.com. I set the keyout and out options to example.com.key and example.com.crt respectively.

1
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout example.com.key -out example.com.crt

If you’re like me and you use the jwilder/nginx-proxy Docker image, it won’t find your certificates unless you follow the naming convention above.

Now, make sure that no one but root can look at your private key:

1
2
3
cd ..
sudo chown -R root:root certs
sudo chmod -R 600 certs

Alternatively, if you need validation from a third-party Certificate Authority, I like to use startssl.com. Their site is a little clunky, but they offer certificates for free, so they’re alright in my books.

See also: Chaining intermediate certificates for Nginx


Chaining intermediate certificates for Nginx

I always use startssl.com to get free authentication certificates. It’s a little clunky to use, but it’s free and that makes it awesome. When it comes time to configure Nginx to use my new certificates, I always forget what to do. These instructions are adapted from here.

Having successfully followed the instructions at startssl.com, you’ll wind up with these four files:

  1. ca.pem
  2. ssl.crt
  3. ssl.key
  4. sub.class1.server.ca.pem

I like to put these all in a directory and zip ‘em up for transport to the production server. Assuming that they’ve all been saved to a directory named for your URL (e.g., example.com/):

1
2
tar -zcvf example.com.tar.gz example.com
scp example.com.tar.gz you@example.com:~

Then, from the production machine, untar the file:

1
2
3
ssh you@example.com
tar -zxvf example.com.tar.gz
cd example.com/

Decrypt the private key with the password you entered at startssl.com.

1
openssl rsa -in ssl.key -out example.com.key

The unencrypted private key is not something you want to show off. Make it so only root can read it:

1
2
chmod 400 example.com.key
sudo chown root:root example.com.key

Nginx needs the startssl.com intermediate certificate concatenated to the public certificate:

1
cat ssl.crt sub.class1.server.ca.pem > example.com.crt

The private key has been decrypted and the public key concatenated. Supposing you have an Nginx server directive that looks like this:

1
2
3
4
5
6
server {
listen 443 default_server ssl;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# ...
}

We need to move the public and private keys into the directory specified (/etc/nginx/ssl/).

1
2
sudo mv example.com.crt /etc/nginx/ssl/
sudo mv example.com.key /etc/nginx/ssl/

Restart your Nginx server and your certificates should be ready to go.