When I first set up Edinburgh Genomics' reporting web app, it was on HTTP without SSL encryption. This was not too much of a concern to start with since our servers were on a closed network, but later as a result of a new feature being added, it became necessary to switch the app over to use a secure connection. This meant setting up SSL certificates in order to get the app's HTTP server to use HTTPS.
An SSL certificate is a file that is cryptographically 'signed' by a trusted
party (either an individual or a CA - certificate authority), to enable secure
and authenticated communication between the the client (e.g. a web browser) and
the server. The process of setting up HTTPS simply consists of obtaining SSL
certificates and pointing the web server to them (in the case of Apache, via
mod_ssl
). This post will describe how I did this on Edinburgh Genomics'
reporting app.
For testing or personal applications it's perfectly possible to use a
self-signed certificate (after all, the most trustworthy party that can sign
your certificate is yourself). Firstly we need an RSA private key. You might
already have one, e.g. in ~/.ssh
, but you can generate a dedicated one with:
[root]# openssl genrsa -out rsa_private.key 2048
Once we have a private key, we can then generate a CSR (certificate signing request):
[root]# openssl req -new -key rsa_private.key > server_name.csr
This enters an interactive prompt, asking you for metadata to be attached to the certificate, including your organisation name and location. This information appears in the resulting CSR file and is used to generate and sign a certificate:
[root]# openssl x509 -in server_name.csr -out server_name.cert -req -signkey rsa_private.key -days 100
A certificate is only valid for a finite amount of time, with the default being
30 days. You'll probably want it for longer than that, so use the -days
option
to specify how long you want the certificate to be valid for. Once you've got
this certificate, you can point your web server to it as below.
While self-signed certificates are fine for testing and personal applications, there is an assumption that you trust the person who signed it. Web clients do not inherently know who to trust, so if you visit a website with a self-signed certificate, the web browser will complain and programmatic interfaces might not work at all. This is because web clients only trust a finite list of known CAs - organisations that are trusted by the worldwide web community to issue signed certificates. It is possible to install your self-signed certificate into the client, but that's an extra stage of setup that won't look very professional to others visiting your website.
The solution to this is to get a certificate signed by a CA. If your organisation has a process for this already, this is a fairly painless task. Firstly you need a private key and CSR, generated the same way as for self-signed:
[root]# openssl genrsa -out rsa_private.key 2048
[root]# openssl req -new -key rsa_private.key > server_name.csr
Then instead of generating a certificate yourself, you send the CSR file to your organisation to be sent for signing.
Once this has happened, your organisation should hand back to you one or more certificate files. You should have an actual certificate for your server, potentially a copy of the CA's root certificate (this is public), and an intermediate certificate linking your server's certificate to the CA's root one. These can now be set up on your server.
Firstly having got the certificates and associated files onto the server (as
root, and with sensible file permissions!), all that really needs done is to set
up mod_ssl
. If not already installed:
[root]# yum install mod_ssl # or apt-get, or whatever
Then update your web app's Apache config:
<VirtualHost *:80> # change this to '*:443'
ServerName <server_name>
# add these 4 lines
SSLEngine on
SSLCertificateFile "path/to/ssl_certificate.crt"
SSLCertificateChainFile "path/to/intermediate_certificate.crt"
SSLCertificateKeyFile "path/to/ssl_key.key"
# rest of the config...
</VirtualHost>
Here, we reconfigure the VirtualHost from port 80 (the default for HTTP) to 443 (the default for HTTPS), and add some fairly self-explanatory SSL parameters. That's pretty much all that's required, however it is possible to go one further.
<VirtualHost *:80>
ServerName <server_name> # this can be the same as above
Redirect / https://<server_url>
</VirtualHost>
Here we've added another VirtualHost on port 80 which we used to be listening
on, and used Redirect from mod_alias
, which here simply redirects any
http://
requests to https://
on the same server. This will ensure HTTPS is
always used without requiring the user to use https://
in their URLs. There
is, however, one caveat in that this only works for GET requests.
I discovered this the hard way when I noticed that POST operations via
Python requests were returning 200 OK
when
they should have returned 201 Created
. Looking at the response to one of
these, I saw that it had been returned as a GET! It turned out that this is part
of the rfc2616 HTTP specification, which states that any request other than GET
or HEAD that receives a 301 Redirect
(which is how mod_alias
redirection
works) may not proceed to the new URL. Python requests was then converting this
into a plain GET request (no query parameters) on the same endpoint. Of course
you don't want this kind of behaviour in production, in which case it's
important to use https://
in the base URL and not rely on redirection. PATCH
requests also failed in Python requests, but at least returned a more sensible
400 Bad Request
.
Overall setting up HTTPS, despite requiring certificates to be requested from a third party, was very straightforward on Apache, with the only caveat being redirection with non-GET requests. I'll definitely be doing this more readily in the future.
Official documentation for Apache
Specifications for request redirection