Sunday, November 14, 2010

Using SSL in Rails Applications

If your web application manages any private information, you should be using SSL (https://) for those parts of the application. Any data sent over a plain http connection can be “sniffed” by any ISP along the route that the packets take if you aren’t using SSL.
Fortunately, implementing SSL isn’t too hard, once you know what you need to do. This article will talk you through the process. These are the steps we’ll go through:
  1. Create your private key
  2. Create your certificate signing request
  3. Get your certificate
  4. Configure your web server
  5. Set up your Rails application

Creating your private key

The first thing you need is a private key, with which your certificate will be encrypted, and which will be used to encrypt your SSL pages before they are sent. You make the private key yourself. Assuming your server has openSSL installed, enter the following command in an SSH shell:
openssl genrsa -out domainname.key 1024
This creates your private key in the file domainname.key (you should substitute your domain name for domainname, of course, although the name doesn’t really matter). This key is unprotected, so you should ensure that this file is readable only by the root user:
chmod 600 domainname.key
Alternatively, you can create your key in an encrypted form that will require you to specify a passphrase when creating the key, and to enter that passphrase to access the key:
openssl genrsa -des3 -out domainname.key 1024
This makes your key more secure, but it has a big downside: once you’ve installed the key, you’ll need to enter the passphrase any time the web server is rebooted. If you’re not around and there’s an emergency reboot of your server, your site will be down until someone can log in and enter the key.
If you do encrypt your key, don’t forget your passphrase! Without it, your key, and the certificate that uses it, will be worthless, and there’s no way to recover it.

Creating your certificate signing request

Now that you have your private key, you can create the certificate signing request (CSR), which you’ll need to get your certificate.
In the SSH shell on your server, create your CSR as follows:
openssl req -new -key domainname.key -out domainname.csr
You’ll be prompted to provide the name of the company, the country, city, and state, and some other information. The most critical piece of information here is the common name, which must exactly match the domain of the server on which the certificate will be used, including any subdomain. Note that www counts as a subdomain, so one certificate works either with www.domainname.com or with domainname.com. You can also buy a “wildcard” certificate, which works with any subdomain, but they’re much more expensive (at this writing, $199 vs. $19.99 at GoDaddy). (To make your site work with or without the www prefix, you should be redirecting accesses to one or the other, so you’ll only need a certificate for one of them.)
Don’t enter any of the “extra” information that you’ll be prompted for. In particular, don’t enter a challenge phrase, or the certificate signing authority won’t be able to read your request.
The file domainname.csr will now contain a string of text, which you’ll shortly be providing to your certificate authority. The file contents should look something like this:
-----BEGIN CERTIFICATE REQUEST-----
MIIBxjCCAS8CAQAwgYUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UE
BxMKU2ViYXN0b3BvbDEXMBUGA1UEChMOTXkgV2ViIENvbXBhbnkxGzAZBgNVBAMT
End3dy5kb21haW5uYW1lLmNvbTEeMBwGCSqGSIb3DQEJARYPbWVAbXlkb21haW4u
Y29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCpyw/LtDRQo/MCtDlckqLz
Cghnod7OFjcGXqprRW15Vn8bTB4v2L29lx2n+U1uK9jWCO/bZFUzVDZ/ESWhCRN8
roqbNuCxBdAzpX2M92RPcZWPeK+cRaJ7kafn5B8kyTXHenYWBAu/epy1NG7fagoO
qV4nqmCv0EwTkeWm9uShjQIDAQABoAAwDQYJKoZIhvcNAQEEBQADgYEAgXpQ6E0/
SyX7r25VI1EoLz2lRBX6tkqhOoBlVAPzDVN88todMaLQlbCz0VXKP9eS78cZe8kJ
7jI+Ujmio7GQ8zFLwpMCXzGCkpri30wO4hsK1lfvC/ScPTEayISECUNlTlRbRztW
W7AH4Z67d47E1hctoitbXSCbQVOfGavm1mA=
-----END CERTIFICATE REQUEST-----

Getting your certificate

Now you need to choose where to get your certificate, and what level of verification to pay for. To understand the choices, keep in mind that an SSL certificate provides two separate functions:
  • It certifies, to varying degrees, the identity of the person or business that controls the web site.
  • It provides the public and private keys for encrypting communications with the site.
You can get certificates at prices ranging from less than $20 to more than $1500, and they all provide the same encryption. The only difference is the degree to which they verify that you are who you say you are, and the seal that they give you to display on your web site. For more on certificates and certificate authorities, see Implementing SSL. Or, if you don’t care about all the fancy stuff (which, for the most part, provides little value) just go to GoDaddy and get your $19.99 Turbo SSL certificate.
The workflow will vary depending on your certificate authority; I’ll describe how it works at GoDaddy.
When you purchase your certificate, you get a credit that is good for one certificate. You then log into the SSL Certificate part of the site and use the credit to request a certificate. The web form provides a form field into which you paste the text from the CSR you generated in the previous step.
When you submit the CSR, the certificate authority will email the administrative contact for the domain to ask if they want to approve the request. This is how they verify that the certificate belongs to the domain. If you purchase a more expensive certificate, at this point they’ll perform additional verification steps to ensure that the certificate belongs to who it says it belongs it.
Once the CSR is approved, you’ll get an email with a link to download a zip file that contains the two certificate files you need to install on your server. One is your certificate, and will be named something like “domainname.com.crt.” The other is the intermediate bundle certificate that establishes the chain that connects your certificate to the signing authority. In GoDaddy’s case, this is gd_intermediate_bundle.crt.
Now is a good time to make a backup of these files. The intermediate bundle is a standard file that you can usually get again, and you can request that your certificate authority re-issue your certificate file. But if you lose the key file, your certificate is worthless, and you’ll have to purchase another one.

Configuring your web server

Now you need to configure your web server for SSL. Just how this is done depends on what server your using, its version, and how it was installed and configured. I’ll describe how it works for a typical Apache 2.x configuration; you may need to adjust this for your situation.
You should already have virtual host definition, either in your httpd.conf file or in an application-specific configuration file that is included into the main httpd.conf by reference. There are two kinds virtual host definitions: name-based, and IP address based. You can’t use name-based virtual hosts with SSL, so you’ll need to have a dedicated IP address for the site that is using SSL. If you only have one IP address for your server, you’ll only be able to have one application. If you want to host multiple applications, your host should be able to provide you with multiple IP addresses.
You’ll need two virtual host blocks, one for the non-SSL parts of the site, and another for the SSL parts. The basic configuration should look something like this:


  Include conf/apps/domainname.conf.common




  Include conf/apps/domainname.conf.common

  # SSL Engine Switch
  SSLEngine on

  # SSL Cipher Suite:
  SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL

  # Server Certificate
  SSLCertificateFile /etc/httpd/conf/ssl.crt/domainname.com.crt

  # Server Private Key
  SSLCertificateKeyFile /etc/httpd/conf/ssl.key/domainname.key

  # Server Intermediate Bundle
  SSLCertificateChainFile /etc/httpd/conf/ssl.crt/gd_intermediate_bundle.crt

  # Set header to indentify https requests for Mongrel
  RequestHeader set X-Forwarded-Proto "https"

  BrowserMatch ".*MSIE.*" \
    nokeepalive ssl-unclean-shutdown \
    downgrade-1.0 force-response-1.0
  
  CustomLog logs/domainname.com-ssl_log \
    "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
Let’s walk through this. The first virtual host block is essentially the same as what you’d have without SSL, except that you have to specify an IP address (123.456.789.123 in this example). I’ve put all the configuration that would normally go here, to set up the proxy to Mongrel and any mod_rewrite rules, into an include file, domainname.conf.common, which is not shown here. Doing so allows the same commands to be reused in the SSL block.
The second virtual host block defines the same IP address, but this time for port 443, which is the standard SSL port. First we include all the common configuration code to handle mongrel and so forth. Then, finally, comes the SSL configuration directives. We need to turn on the SSL engine, specify the encryption to be used, and point to the three files we generated earlier: the key, the certificate, and the intermediate bundle. (The CSR isn’t needed once the certificate has been generated.) In this example, I’ve shown them going in subdirectories of conf, but you can put them anywhere, as long as these pointers match the location.
Next, we set the request header to tell mongrel when there is an https connection. Don’t leave this out, or you’ll have no end of confusing problems in your Rails code!
Finally, there’s a bit of config to keep Internet Explorer happy, and a custom log definition to log SSL accesses to a separate log file (this last bit is entirely optional).
To test all this, enter
apachectl configtest
in an SSH shell. This will check your config files for syntax errors without actually rebooting the server, so in case there’s a problem you don’t leave the server disabled while you sort out any errors.
At last, you can now reboot Apache (sudo service httpd restart), and if everything is correct, you’ll have an operating SSL server.

Tell your Rails app where to use SSL

With the above setup done, you should be able to browse to any page in your application using either http:// or https://. You’ll probably want to leave parts of the site unsecured, since SSL pages put extra load on the server, so you need a way to indicate which pages require SSL, and which don’t.
The ssl_requirement plugin, written by DHH himself, makes this simple. Install the plugin:
script/plugin install ssl_requirement
And then include it at the top of your application.rb file, which effectively includes it in every controller:
include SslRequirement
Now all you need to do is specify, at the top of each controller, which controller actions require SSL:
ssl_required  :login, :account, :payment, :cart
Be sure to include in this list any create or update actions that process form data from SSL pages. The magic of this is that you don’t need to change any links; any access to an action that requires SSL will automatically redirect to https://. If anyone tries to access a page that is supposed to be secure with an http:// link, they’ll be redirected.
You can also provide an ssl_allowed declaration if there are actions that you want to be able to access either way. Such actions won’t redirect to https:// but if that protocol is explicitly specified in a link, it will work.

Check your Ajax actions

For the most part, that’s all there is to it on the Rails side. There’s just one more thing that may require your attention: actions that service Ajax requests.
On any page accessed with SSL, all Ajax requests must use SSL, or they will fail. To make this happen, all you need to do is include the names of the actions that service the requests in your ssl_required statement.
In some cases, you may have Ajax actions that have no code in the controller, but are just rendering RJS templates or other views. For these actions, you’ll need to add an empty action definition block in the controller, and add the action name to the ssl_required statement.
One obscure case is the text_field_with_auto_complete helper. If you’re using this in your view, you’re probably also using the auto_complete_for :model :field shortcut in your controller. This automatically produces an action to respond to the autocomplete requests. But since the action isn’t explicitly defined, how do you tell it to require SSL? You just need to know what the automatically created action is named, which is auto_complete_for_model_field. Just substitute the names of your model and field, and add this action name to your ssl_required statement.

Check for mixed content

One last thing to watch out for is mixed content. If you’re testing in Firefox, you won’t notice it, but in IE, users will get a security warning if an SSL page accesses any non-secure content. You don’t have to worry about links to images and so forth, as long as they’ve been written as paths without the full domain name; those will automatically use the protocol of the main page. But if you have any full links, you’ll need to make sure they are written as “https://” on any SSL page.
One case where this problem can sneak in is with JavaScript snippets for services such as Google analytics. If you have a Google analytics link on your page, change it from “http://www.google-analytics.com/urchin.js” to “https://ssl.google-analytics.com/urchin.js”. (Google has now change its analytics code so this isn’t necessary if you’re using their current code.) This will work fine on all pages, whether they use SSL or not, and it will keep the analytics link from triggering the mixed content warning.

Now you have SSL, but …

Now your site can send pages in encrypted form, so they can’t be snooped on by prying eyes along the path between your server and your user’s computer. And the user can examine the certificate to see to whom it was granted, and know that the browser would warn them if the domain name didn’t match.
Of course, there’s much more to keeping your users’ data secure. You need to ensure that all model requests are properly scoped, and for internal security, you need to make sure that confidential information isn’t leaking out in your logs, exception reports, and backups.

1 comment:

  1. nice information its usefulness and significance is overwhelming the way you covered all the basic necessary information is really impressive good work

    ReplyDelete

Please keep your comments.