Moving to HTTPS, Rails force_ssl and Rollback

Background
We recently moved the Getup site from mixed HTTP/HTTPS to completely HTTPS. The primary driver was to ensure that sessions were never sent in plain text over the wire, to avoid session hijacking. There are other benefits too, such as protecting personal details from eavesdropping over the wire, proving site authenticity and generally simplifying the code. Checking around the web, Twitter is HTTPS only, and even Google search is all HTTPS (when you are logged in).

Rails HTTPS and force_ssl
The easiest and simplest way of moving a Rails 3 app to all HTTPS is to simply set force_ssl = true in relevant environment files. This then causes Rack ssl middleware to be loaded as the first middleware. As you can see from the code, this middleware does a variety of good stuff, that really ensures once HTTPS, always HTTPS!

  • 301 permanent redirect to HTTPS (cached forever in most browsers so you never hit the http url again)
  • Secure on the cookies so they can never be sent over HTTP (important they don't accidentally go with a redirect for example!)
  • HSTS to ensure that in supported browsers, nobody can ever go to HTTP again!

HSTS
HSTS (HTTP Strict Transport Security) is offered by Chrome and Firefox ensures that for a given time (usually a long time, eg 20 years in the case of Twitter!), it is not possible to go to the site over HTTP again. To activate this, the site only needs to send the Strict-Transport-Security header once. You can check and manage what's in your HSTS store in Chrome with chrome://net-internals/#hsts

Rollback
When moving to HTTPS, we wanted to ensure we could rollback to a previous release in case of problems. Using force_ssl out of the box precludes this - if you roll back after a 301 redirect or HSTS loaded by a client browser, your site will no longer be accessible!

We used a small monkey patch which turns off HSTS and uses a 302 temporary redirect, rather than a 301. This means that rollback to a previous release works fine. Here's the patch:

require 'rack/ssl'

module ::Rack
  class SSL

    def redirect_to_https(env)
      req        = Request.new(env)
      url        = URI(req.url)
      url.scheme = "https"
      url.host   = @host if @host
      status     = 302
      headers    = { 'Content-Type'  => 'text/html',
                     'Location'      => url.to_s,
                     'Cache-Control' => 'no-cache, no-store'
                   }
      [status, headers, []]
    end

    def hsts_headers
      {}
    end

  end
end

This patch is only needed temporarily, until you decide that you no longer would want to deploy a release before force_ssl.

Performance
So is HTTPS slower than HTTP? It is to start with in initiating the first request, as ssl needs to be negotiated and set up. This leads to a few more round trips. If your clients and servers are in same country, this is pretty insignificant. Round trips from Australia to USA for example are more significant but not a major stumbling block, as long as you use Keep-Alive on the connection to ensure that later requests re-use the set up from the first request.

Assets are fully cacheable over HTTPS using the usual HTTP headers, and have been even since early Internet Explorer versions. You do need to make sure that you always load HTTPS assets though, to ensure you don't get mixed-mode warnings in the web browser. We found some useful HTTPS performance tips here.

Load on the servers was not an issue for us as we are using Amazon Elastic Load Balancers (ELB) for our HTTPS implementation. The web/app servers don't get involved as they are just reverse proxied by the ELB, which manages the HTTPS sessions.

Redirect Gotchas!
We have a few other domains which simply redirect to the canonical www.getup.org.au domain. Out of the box, the rack ssl middleware loads first, before our redirect middleware. This meant that for these additional domains, we got a nasty certificate warning in the browser as it is sent to HTTPS first (on the wrong domain), and then gets the redirect to the canonical domain that has the valid certificate. Changing the order of middleware to do redirects first, and then HTTPS is an easy solution.

Conclusion
The move to full HTTPS has gone smoothly and we didn't end up needing rollback. However, it was worth having the monkey patch so that rollback was possible as an insurance policy against unexpected major problems.


Actions

Information