Diffie-Hellman for TLS

After successfully enabling Let's Encrypt for my domain, I ran the test over at SSL Labs and was disappointed to see I only scored a C grade.

It seems that because my Nginx configuration supported SSLv3 it was susceptible to the POODLE attack. And thus regardless of anything else the grade was capped at C. This was easily fixed however by adding the following line to nginx.conf:

http {  
    ...
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ...
}

I ran the test again but this time only scored a B grade. Here's why:

Warning! This site uses a commonly-shared 1024-bit Diffie-Hellman group, and might be in range of being broken by a nation-state. It might be a good idea to generate a unique, 2048-bit group for the site.

Luckily, the kind folks over at weakdh.org provide a Guide to Deploying Diffie-Hellman for TLS. To summarise, do the following:

First thing to do is generate a dhparams.pem. I put it in my /etc/nginx/sites-available directory, but it doesn't really matter where you put it as long as it's a fairly safe location:

openssl dhparam -out /etc/nginx/sites-available/dhparams.pem 2048  

Now in the server block for your domain (by default this is in /etc/nginx/sites-available/default) add the following lines:

server {  
    ...
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/sites-available/dhparams.pem;
    ...

Now reload Nginx:

sudo nginx -s reload  

These simple steps have garnered me an A grade.

A grade on SSL Labs

Implementing Let's Encrypt

Updated Now that Let's Encrypt is in public beta I've updated this to suit.

These are exciting times for the web. Secure connections have been around for a long time and are now expected and trusted by the public. Unfortunately they're also very expensive, usually around $99 per year.

But huzzah and hooray, we now have Let's Encrypt, a service from the EFF, Mozilla, Cisco and others to provide FREE TLS encryption for any website. You can read more about the mission here: https://letsencrypt.org/about/

At the moment the service is in public beta which means they're still ironing out the bugs, but it's currently serving tens of thousands of certs and seems to be holding up fine.

For the purposes of this little walk-through I'm using Ubuntu 14.10 and Nginx.

One of the goals of Let's Encrypt is that it be automatic. In time this will include configuring the server too (be that Apache, Nginx, or another) and it already does a pretty good job but there's definitely more work to do.

I'm going to show how get just the cert files and configure Nginx manually. So to start with clone the Let's Encrypt software somewhere, I put it in my home directory:

git clone https://github.com/letsencrypt/letsencrypt  

Ideally before the next step make sure you stop any service using port 80. There is a 'webroot' option to circumvent this and you can read more on the How it works page. But in my case I'm happy killing Nginx for a couple of minutes:

sudo service nginx stop  

Now cd in to the new directory and run the software with the following parameters:

cd letsencrypt  
./letsencrypt-auto certonly

The first time you run the program it will ask for an email address and ask that you agree with the T's and C's.

Lets Encrypt email address screen

Lets Encrypt terms and conditions screen

Finally it will ask for your domain(s). Separate these either with a space or a comma.

That's it! If all goes well the necessary files will be created here: /etc/letsencrypt/live/<yourdomain>/

To add to Nginx, add or change your sites-available file to look like this:

server {  
    listen 443 ssl;
    server_name lewiswalsh.com;
    ssl_certificate     /etc/letsencrypt/live/lewiswalsh.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/lewiswalsh.com/privkey.pem;

Next I created a server block to redirect https://www.lewiswalsh.com to https://lewiswalsh.com and one to redirect the insecure versions of those domains:

server {  
    listen       443 ssl;
    server_name  www.lewiswalsh.com;
    return       301 https://lewiswalsh.com$request_uri;
}
server {  
    listen       80;
    server_name  lewiswalsh.com www.lewiswalsh.com;
    return       301 https://lewiswalsh.com$request_uri;
}

Restart Nginx and all should be well.

sudo service nginx restart  

Since Let's Encrypt certificates expire every ninety days you'll need to manually renew. Eventually this can be automated, but for now just run the following command again when your certs expire:

./letsencrypt-auto certonly

While you're at it, you may as well beef up the Diffie-Hellman cyphers to get that A-grade SSL.

SSH config

I first encountered the SSH config when I had to set up SSH for two BitBucket accounts. One work, and one personal. Requiring separate login credentials, setting the remote on Git repositories to ssh://git@bitbucket.org/... wasn't going to work.

Each BitBucket account requires a different public SSH key so for two accounts I needed to generate two sets of keys. You'll likely already have a keyset called id_rsa so call the new one something else:

$ ssh-keygen 
Generating public/private rsa key pair.  
Enter file in which to save the key (/Users/you/.ssh/id_rsa): bb-personal  

Now in your ~/.ssh/ directory create a new file simply called config, for my example it looks like this:

Host bitbucket-work  
  User git
  Hostname bitbucket.org
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/bb_work

Host bitbucket-personal  
  User git
  Hostname bitbucket.org
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/bb_personal

Now instead of bitbucket.org in my git remote URIs I substitute the relevant Host from my config:

ssh://git@bitbucket-personal/...  

Of course, you need to ensure the public key is configured over on BitBucket, GitHub etc.

To use config with your own remote machines is just as easy. You can use any of the keys you already have on your system, or create a new one as shown above.

If it doesn't exist, create a file in the ~/.ssh/ directory on your remote machine called authorized_keys and copy the public key in to it, each one on a new line. Never the private key!

Now in your config file on your local machine create a new host for your remote machine:

Host me-production  
  User lewis
  Hostname <your host or ip>
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/id_rsa

Strictly speaking, I don't think the User line is necessary.

Now all I need to type in the terminal to ssh in to my production box is:

$ ssh me-production

Persistent NodeJS apps on restart

As far as I'm concerned the only two decent solutions to keeping NodeJS apps running properly are PM2 and Forever. The each have their strengths and weaknesses which is a matter for another post.

I recently found myself in a position where I had to use both. I had an application I'd written that I wanted to run in Node 4.x.x and a Ghost install that insists on Node 0.10.40. I use NVM to manage different versions on the same machine and found it best to use PM2 for one app and Forever for the other. Not very elegant, but it works and is holding up.

After running for a few days I patched the server OS and it dawned on me that if I restarted the server, I'd have to manually restart both Forever and PM2. So I present the method I used to have my apps launch properly using SystemV init system. This should be fairly easy to port to other init systems.

First thing to do is create new startup and shutdown scripts:

touch /var/www/startup.sh  
touch /var/www/shutdown.sh  

The first line in my startup.sh 'sources' the nvm script, otherwise bash complains it can't find it. So here is the startup script:

. ~/.nvm/nvm.sh
export NODE_ENV=whatever_this_should_be  
nvm use 4.2.1  
cd /var/www/webapp-one/  
pm2 start index.js  
nvm use 0.10.40  
cd /var/www/webapp-two/  
forever start index.js  

Hopefully the above is self-explanatory, it's just a series of commands to switch to the right Node install and get things up and running.

It's also important to have an elegant shutdown script, so here's mine:

. ~/.nvm/nvm.sh
nvm use 4.2.1  
cd /var/www/webapp-one/  
pm2 kill  
nvm use 0.10.40  
cd /var/www/webapp-two/  
forever stopall  

As before, we still need to 'source' nvm, then again it's just a series of commands to elegant shutdown.

Make both of these files executable:

chmod +x /var/www/startup.sh  
chmod +x /var/www/shutdown.sh  

It's a good idea here to test they work, hopefully you'll know by looking at the output:

cd /var/www  
./startup.sh
./shutdown.sh

Now we can get init.d to manage the startup and shutdown for us. So in the /etc/init.d directory create a new file:

sudo touch /etc/init.d/myapps  

Init scripts follow a certain format, the following is for SystemV:

#! /bin/sh
WWW_DIR=/var/www/  
case "$1" in  
  start)
    su myuser -c $WWW_DIR/startup.sh
    ;;
  stop)
    su myuser -c $WWW_DIR/shutdown.sh
    sleep 3
    ;;
  restart)
    su myuser -c $WWW_DIR/shutdown.sh
    sleep 3
    su myuser -c $WWW_DIR/startup.sh
    ;;
  *)
    echo "Usage: websites {start|stop|restart}" >&2
    exit 3
    ;;
esac  

In the above file it should obvious what each section does. Pre-pending our bash scripts with su myuser -c tells the script to run the command as a certain user, you of course will pick whatever your username is. sleep simply pauses the script just to let things settle.

Once you've saved it, make it executable for all users, and test it out:

sudo chmod a+x /etc/init.d/myapps

cd /etc/init.d  
sudo ./myapps start # to test startup  
sudo ./myapps stop  # to test shutdown  
sudo ./myapps restart # to test restart  

If all is good you can update SystemV to run at system startup and shutdown:

sudo update-rc.d myapps defaults  

Now you can manage your apps like any other service.

sudo service myapps start  
sudo service myapps stop  
sudo service myapps restart  

Personally I prefer to have one service to handle everything. If I need to manage an individual app I'll do that manually, but you could create separate init.d scripts for all your separate apps.