software engineer | amateur musician | backpacker wannabe

Daily archives of “March 25, 2015”

Intro

So we have our RaspberryPi ready, with MinibianPi installed and secured. If you’re like me, there’s already a ton of ideas about what kind of systems I want to set up in my house, and this is just from browsing and reading some awesome blog posts. What many people forget is that when you’re about to jump into the “smart home” or “personal cloud” bandwagon, there’s one thing that I believe you should have ready from day one: a secure reverse proxy.

Enter the RaspberryPi-Nginx-SSL combo.

Disclaimer

I am by no means an expert in server installation or maintenance. I do this for fun, and sometimes I get lazy and cut corners. Please don’t treat this as a you-must-do-this guide, but more of a this-is-what-I-did and I’d like to share it with you. If you spot any mistakes, please let me know and I’ll do my best to address it immediately.

Objectives

What does a reverse proxy do, you say? Here‘s a good read. Now, why would a home user like me need to have a reverse proxy? Here are my reasons:

I want to have a single point of entry for all inbound traffic for my internal systems, using a single domain (or subdomain). All other systems (running on the same RaspberryPi or another unit downstairs) will be assigned a “virtual directory”.

I want to have all said inbound (and outbound traffic) encrypted using the strongest and latest cipher suites and have all reasonable optimization and security features enabled.

I want to have all internal traffic to be sent in cleartext to reduce the load of encryption protocols on my internal systems, and have a termination proxy do all the encryption work.

Your reasons may be different, and your mileage will surely vary. As with any tutorial you’ll find on this site, this is more of a “this-is-how-I-did-it” rather than “you-should-follow-my-example”. Proceed with your own considerations.

Why Nginx?

That question will probably spark a debate longer than this article. I’ve heard that Nginx is much more lightweight than Apache, and more suitable as a reverse proxy. But don’t take my word for it. Googling “nginx vs apache” will get you situated.

Prerequisite

This build is based on my previous article on how to set up a secure MinibianPi-based server. It’s up to you if you need as much security as I do.

Setting Up the Firewall

Previously, we’ve put a firewall in place that will only allow SSH connections. We’ll have to change it so that it allows HTTP and HTTPS (ports 80 and 443) from anywhere to connect.

Open the firewall rules file

sudo nano /etc/iptables.firewall.rules

Add the following lines anywhere in the file as long as it’s above the default deny statements (the “Drop all other inbound” line)

To be able to run the script, we need to make it executable. Let’s make it executable only for us and deny access to anyone else, because we’re paranoid like that.

sudo chmod 700 build_nginx.sh

Now all that’s left is run the script. This will take about 20 minutes, could be more. If you want to grab a beer, now’s the time.

sudo ./build_nginx.sh

After the build is done, check the output message and make sure that there are no errors. No errors? Great! Let’s open our browser and browse to the Pi’s address to check. If you see the Nginx welcome page, you’re all set. If not, recheck your build output, you might’ve missed something there.

That’s it! Now you have Nginx installed and ready to use. But of course the vanilla installation always has loopholes that we must fix. Onwards!

Optimizing Nginx Configuration

We could tune our Nginx installation to suit our needs. What I’m about to mention here are what I think would be beneficial for my setup. Your mileage will vary, so please take it with a grain of salt, do your research, and make an informed decision about your own setup.

All of the modifications in this section are done in the nginx.conf file, so let’s open it up:

sudo nano /etc/nginx/nginx.conf

First on my checklist is the number of worker processes. This is probably best set equal to the number of cores available on your processor, that way a worker process can be “attached” to a core. I use RaspberryPi 2 Model B, which has a quad-core processor, so I’ll be using 4 worker processes:

worker_processes 4;

Next, I want to optimize gzip compression, which will allow our server to save a bit of bandwidth when serving our content. Most of the settings are already there, I’m just adding a minimum length and some content types. Again, do your research and add content types or tweak settings as you see fit:

If you’re wondering, gzip_comp_level is how much compression we want. It accepts values from 1 to 9; 1 is the least compressed but fastest to compute and 9 is the most compressed but slowest to compute. With the limited power of our minuscule processor, I’m going with a middle ground value of 6.

Now, I want to have basic protection against DDoS attacks. I do this by limiting the timeouts for several events. Note that some of the settings below may already be present in the file, so do make sure that you’re not mentioning any duplicate settings (Nginx configtest will complain about it if you do, so don’t worry):

Last, to support longer domain names, I want to increase the hash bucket size. I find that 128 works best, but set it as you see fit:

server_names_hash_bucket_size 128;

That’s all there is to it. Let’s proceed with configuring our website.

Configuring the Website

This section assumes you already have dynamic DNS setup for your public IP address. If you haven’t got that setup yet, you can read my article on the topic and come back here to proceed.

It’s probably considered a good practice to not mess with the “default” settings; just copy it and work with a new file. That way if anything goes awry we can always revert to default. Here’s the complete set of commands that should get you set up, all in one go:

After executing that command we should have a copy of “default”, which is named “mysite”, and it is enabled. Now let’s open it up and edit it:

cd /etc/nginx/sites-available && sudo nano mysite

I don’t like too much comments in my config, so I just delete all lines that begin with “#”, then save and close. After that, let’s do a config test:

sudo service nginx configtest

If no errors show up, let’s reload Nginx to make the new settings take effect:

sudo service nginx reload

All done!

Securing Connections with Encryption

SSL Certificates

To encrypt communications between our server and the internet, we need to have an SSL certificate in place. I’m not going to go too deep into explaining what an SSL certificate is, because (1) I’m not that well-versed in cryptography and (2) this article is supposed to focus on the technicalities of implementing such encryption. If you’d like to know more, there are a lot of articles that you can read elsewhere.

Preparing a Private Key

To obtain an SSL certificate, we must first create a private key. This key must be known to no other than ourselves and must be kept secret in a safe place, preferably off-site and offline, to prevent unauthorized access. But that’s for the big players. For us, safekeeping in a USB flash drive that’s not plugged in unless needed will probably be sufficient.

Here are the set of commands we need to issue to generate our private key, which will reside in the /etc/nginx/ssl/ directory:

I recommend replacing ‘mysite’ with the domain name the certificate will be issued for to avoid further confusion.

The command starts the process of CSR and Private Key generation. The Private Key will be required for certificate installation. You will be prompted to fill in the information about your Company and domain name.

It is strongly recommended to fill all the required fields in. If a field is left blank, the CSR can be rejected during activation. For certificates with domain validation it is not mandatory to specify “Organization” and “Organization Unit” -you may fill the fields with ‘NA’ instead. In the Common Name field you need to enter the domain name the certificate should be issued for.

Please use only symbols of English alphanumeric alphabet, otherwise the CSR can be rejected by a Certificate Authority. If the certificate should be issued for a specific subdomain, you need to specify the subdomain in ‘Common Name’. For example ‘sub1.ssl-certificate-host.com’. I just used: mysite.com.

Once all the requested information is filled in, you should have *.csr and *.key files in the folder where the command has been run.

The *.csr file contains the CSR code that you need to submit during certificate activation. It can be opened with a text editor. Usually it looks like a block of code with a header: “—–BEGIN CERTIFICATE REQUEST—-” It is recommended to submit a CSR with the header and footer.

The *.key file is the Private Key, which will be used for decryption during SSL/TLS session establishment between a server and a client. It has the header: “—–BEGIN RSA PRIVATE KEY—–“. Please make sure that the private key is stored somewhere safe and secure as it will be impossible to install the certificate without it on the server afterwards.

Generating/Requesting a Signed Certificate

There are a few options where we can get a signed SSL certificate.

If you’re OK with a self-signed certificate (and the annoying warnings you get everywhere you connect to your home network), then this is the way to go. To sign your own certificate, issue the following command:

If you want to have a Certificate Authority sign your SSL certificate, you can apply for it at one of the Certificate Authorities. Just google “cheap ssl certificates” and you’ll get a list of candidates you might want to look at. I use one from GoGetSSL which at the time of writing costs $12.35 and is valid for 3 years. Just register at the provider of your choice and follow their instructions. However, keep in mind that you should never send your private key to anyone. Only submit your CSR.

Either way, we should end up with a *.crt file in our hands, and this is what we will use to secure our server using SSL/TLS. Before we can use the certificate, though, we must chain it together with the CA’s certificates. Here’s a pretty good article on COMODO’s site on how to do that.

Setting Up Nginx for SSL

After we have our certificate signed and ready, it’s time to configure Nginx to use those certificates. To make things easy to maintain, we’ll create another file that we will include in the main configuration file. The file will reside in the /etc/nginx/conf.d/ directory; Nginx is preconfigured to scan that location for additional configuration files.

If you would like to use different security settings or different certificates for your virtual hosts, you can put the additional configuration file outside of /etc/nginx/conf.d/ and include it manually in your http block in /etc/nginx/sites-available/mysite. Let’s create the configuration file:

sudo nano /etc/nginx/conf.d/security.conf

The list below will explain bit by bit what each setting will do.

Set Supported Protocols
We do this to support TLSv1 as the minimum standard because anything below that would compromise our security.

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Set Server Cipher Preference
This will tell browsers to let our server decide what cipher suites to use throughout the session.

ssl_prefer_server_ciphers on;

Set Supported Cipher Suites
This defines what cipher suites we are supporting. I researched quite a bit for this list of cipher suites, and I finally settled using the modern compatibility cipher suite example from Mozilla. (actually that article is a pretty darn good resource, so please read it when you have the chance)

Enable Forward Secrecy
Forward Secrecy (or Perfect Forward Secrecy) is how servers and clients communicate using keys that are never sent through the wire using what’s called a Diffie-Hellman handshake. This ensures that should our private key be compromised, an attacker will not be able to decipher past communications. To perform Diffie-Hellman handshakes, both the client and server needs to send a prime number in cleartext. A Diffie-Hellman parameter file will determine the size of the prime number that is used. The bigger the safer, but generating a big file will take a lot longer. Mozilla recommends a minimum of 2048 bits, but I decide to use 4096 bits. It takes forever to generate on a Pi, so decide for yourself if you want that much bits. To use the parameter file, we include this in our security configuration file:

ssl_dhparam /etc/nginx/conf.d/dh4096.pem;

This setting only defines the dhparam file location. We need to generate the dhparam file by issuing the following command:

cd /etc/nginx/conf.d && sudo openssl dhparam -out dh4096.pem 4096

Enable HTTP Strict Transport Security (HSTS)
HTTP Strict Transport Security is a feature that will make browsers remember to always connect to our server using HTTPS. The first time a browser connects, it can use HTTP, but all subsequent connections will be “forced” to use HTTPS. The max-age here is set for 365 days (equivalent to 31536000 seconds). Mozilla recommends a minimum max-age of 6 months.

Enable OCSP Stapling
OCSP Stapling is a mechanism to “staple” certificate information from our own server so that we can skip contacting the Certificate Authority every time we need to authenticate an encrypted connection. This has the benefit of not sharing any request details to the Certificate Authority (thus increasing our privacy) and relieving the Certificate Authority of precious clock cycles that it would otherwise have to provide to authenticate every request to our server.

This tells Nginx to enable OCSP stapling and verification of OCSP responses, sets the location of our trusted certificates file, and configures Google’s public DNS as our resolver. Details of each setting can be found in the Nginx documentation. To use this feature, we must also prepare the comodo.trusted.pem file (COMODO is my CA, yours may be different). We do this by chaining all certificates like we did before except our entity certificate (the innermost certificate).

Enable Session Resumption
Session resumption is a feature that will allow a full SSL handshake to be abbreviated during subsequent requests. This shaves previous milliseconds off, which can decrease latency and improve efficiency. Here‘s a primer you can read to get acquainted with the concept. To enable session resumption both by cache and by session tickets, add the following lines:

ssl_session_cache shared:SSL:10m;
ssl_session_tickets on;

Enable HTTP Private Key Pinning (HPKP)
I would like to start off by mentioning that at the time of writing this feature is not yet widely used, and I’m not even sure that it’s already a standard. Here’s the draft document if you’re interested in the hairy details. Suffice it to say for now that you should enable this feature only if you’re sure you know what you’re doing and what the consequences are. Please don’t blame me if your site’s visitors come complaining to you about authentication errors that only time can fix. You’re still on board? Well let’s go then! To generate your certificate’s SPKI pin, issue the following command:

Take note of the result. Now, before we actually commit to setting the HPKP header, I strongly urge you to generate a backup set of private key and CSR. This is considered a best practice to help you when your current private key is compromised and you need to revoke your certificate. If you don’t set this now and don’t have a backup, you’ll have your visitors stuck with invalid pins and they will have to wait out until the HPKP header has expired to be able to access your site with new certificates. Finally, to set the headers, insert the following in your security.conf file:

This should force all HPKP-compliant browsers to stop the SSL handshake if they detect that your public key has changed or been tampered with.

The security settings are now in place and ready to go, so let’s save and close the file. Next, we’ll configure the Nginx virtual host to use HTTPS exclusively.

Setting Up Nginx Virtual Host to Support SSL

This section is fairly easy to do. To do this, let’s open up the virtual host file that we’ve set up before:

sudo nano /etc/nginx/sites-available/mysite

We’ll introduce the following changes into the file:

Set Existing Server Block to Listen to Port 443 (HTTPS)
The existing server block we have in place listens to port 80 (HTTP). We need to change this so that it listens to port 443 (HTTPS) instead, and while we’re at it why not throw in SPDY support as well:

Add New Server Block to Listen to Port 80 (HTTP)
We can’t have a server that doesn’t listen to port 80 (HTTP), because most users will type in an address and the browser will assume that it’s HTTP. So, we create a new server block and place our redirection directives there:

After we’ve made the changes above, let’s save the file and exit, and perform a configuration test:

sudo service nginx configtest

If that went well, we can go ahead and reload the configuration:

sudo service nginx reload

Now, we’re ready to test it.

Testing

Finally after all the security settings are in place (don’t forget to make copies of your private keys and store them somewhere safe!), it’s time to test. I like to use SSL Labs to test my settings, and I suggest you do too. Just head over to https://www.ssllabs.com/ssltest/index.html and enter your domain there. If you spot anything out of place, they have a lot of articles that can help you address it appropriately.

Here’s my test results:

If you’d like to see the details of the test, just go over to SSL Labs and run a test on this website.

What’s Next?

Now we have a secured Nginx reverse proxy that handles all incoming HTTP and HTTPS requests. We can tweak these settings as we see fit, and as more internal applications need outside access, but the scope of this article stops here.

As I write more articles based on this setup, I’ll update and post links in this section. So, stay tuned!

References and Further Reading

These articles have been instrumental in my writing this article and some of them may provide more insight for you if you’d like to dive deeper into the “why” instead of the “how”: