Securing Padrino apps with https and ssl on Heroku

5 minute read

Let’s Encrypt has done an amazing job of making https the new normal for web sites and helping create a more secure and private internet by giving away free, automatic ssl certificates to domain owners.

You should be encrypting your web traffic (and need to, to take advantage of new protocols like http/2. Some implementations have stated they will only support http/2 over an encrypted connection. And currently, no browser supports http/2 unencrypted afaik.). Add to this the fact Google will start penalizing non-secured sites in search results, and https is fast becoming the de facto standard.

What does this mean if you’re running a Padrino application?

Heroku has integrated Let’s Encrypt and provides a great, fire-and-forget Automatic Certificate Management system for any paid (even Hobby) dyno on their system.

It turns what used to be a difficult, complex process of certificate management, configuration, and Rack middleware chains (which were quite old), and that could not use padrino’s native Sinatra-based sessions management, into dead-easy ssl and https provision. Since I hadn’t seen a post detailing this, and most posts dealing with getting padrino and ssl going together were quite old, I wanted to write something google-able to save other people the effort of puzzling this out.

You can do everything I’ve described below from Heroku’s well-designed web interface. However, I tend to favour and drive most things off the excellent Heroku CLI. If you haven’t got it installed, and are on OSX, use homebrew and simply brew install heroku. With the heroku cli you can do everything from the command line (as well as script), which is handy if you’re big on the terminal. If you’re on linux or windows, alternative installation instructions are here. Both homebrew and the heroku cli are going to make your dev life easier if you’re not already using them.

Setting up Padrino

At time of writing, I was using Padrino v. 0.14.0.1.

In general, in your app(s), you just need to set

1
2
# app.rb
enable :sessions

You probably already have this set if you’ve used the padrino g admin generator for creating an admin panel, or are using any sort of functionality which requires logging in or somehow managing session state. This turns on Padrino’s native sessions management, which uses Sinatra’s sessions and is handy since you can use the session hash between routes and views. It stores all the data in the cookie. Also, make sure to check that your app has a random session secret set in config/apps.rb, which should be a random 64 character hexadecimal string already created by Padrino for you in the Padrino.configure_apps block (nb: you can also enable: sessions there for all apps in the Padrino application if you prefer rather than setting it in app.rb).

There are other ways to get sessions in Padrino, including using Rack::Session::Cookie and Rack::Session::Pool. The former is useful if you need to add extra parameters to the cookies (for example, expiry) and the latter is faster than pure cookie-based sessions as it only stores an ID in cookie but maintains the rest of the session hash in an instance variable called @pool that’s available to you (but the session data does not persist across app restarts). My use case meant Padrino’s native sessions simplified a lot. YMMV.

Setting up DNS

To have https working, you need to have a qualified domain name for your application. This is fairly simple on Heroku, and just a matter of making sure you have a CNAME record in your DNS provider records (I personally use the excellent DNSimple) pointing at your app on heroku and making sure you’ve let your Heroku app know it’s the endpoint for that CNAME by having set the qualified domain in the app’s Settings | Domains and certificates section in the web interface and explicitly hitting the Add domain button and adding it in.

You can also perform this from the cli with

1
🚀  ~ heroku domains:add cool.example.com --app your_app_name

One gotcha on this: You need to make sure you have your DNS pointing at the correct herokudns.com domain in your application (an issue may be older apps that may be pointing at herokuapp.com or similar) If this is you, it might make sense to create and redeploy the app on the newer heroku stack to support this rather than workarounds, and repoint your DNS. If you do not have your DNS configured and ready when you upgrade the dyno and trigger ACM, certificate verification will fail.

You can verify that you’re actually pointed at herokudns.com by running

1
🚀  ~ heroku domains --app your_app_name

If it comes up looking something like this with the herokudns.com,

1
2
3
4
=== your_app_name Custom Domains
Domain Name                DNS Target
─────────────────────────  ───────────────────────────────────────
cool.example.com           cool.example.com.herokudns.com

you’re good to go.

Setting up the SSL Certificate and https

This is usually the most painful part of getting ssl going on any system. I have to say Heroku has done an amazing job of making this utterly painless through their Automatic Certificate Management. Big fan.

Just upgrade your dyno from Free to Hobby (or more). At time of writing, a Hobby dyno costs $7 USD/month/dyno.

1
🚀  ~ heroku ps:resize hobby --app your_app_name

When you upgrade your Heroku dyno(s) from the Free tier to Hobby, the ACM will automatically start generating you a cert. It does take about 45-60 minutes, but you can obsessively check on its progress with:

1
🚀  ~ heroku certs:auto --app your_app_name

As near as I can tell it’s fairly automatic and foolproof as long as you have your DNS configured and pointing correctly. Heroku did a great job with this.

When the response to the above command comes through with the Domain and an OK in the status,

1
2
3
4
5
6
7
8
9
10
11
12
13
=== Automatic Certificate Management is enabled on your_app_name

Certificate details:
Common Name(s): cool.example.com
Expires At:     2017-07-19 15:03 UTC
Issuer:         /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
Starts At:      2017-04-20 15:03 UTC
Subject:        /CN=cool.example.com
SSL certificate is verified by a root authority.

Domain                     Status
─────────────────────────  ──────
cool.example.com           OK

you’re ready to move on to restricting the app to just https traffic.

Setting up the app for https only

Now, what if you want to ensure that all traffic that gets sent to your app, even if someone is using http goes through https to the app?

You could use some of the aforementioned middlewares: the Rack::SSL and the Rack SSL Enforcer gems seem popular. However, they break cookies if you don’t disable native padrino/sinatra sessions, meaning you can’t use padrino’s native enabled: sessions . Since I didn’t want to mess with the existing sessions functionality I already had built, tested, and working (and, as some of those middlewares had not been updated in years), I went a simpler route.

An easier solution is to simply use a before filter on the app(s):

1
2
3
4
5
6
# app.rb
  unless RACK_ENV == 'development'
    before do
      redirect request.url.sub('http', 'https') unless request.secure?
    end
  end

This seemed simple, explicit, code-transparent, and meant I didn’t have to worry about the extra complexities of a Rack middleware chain breaking padrino’s Sinatra-based session management. All this does is test to see if the request is secure, and if it isn’t, redirects it to the https URL before processing. It ignores the directive in your development environment (I should note here I have ssl set up in both my production and staging environments. If you only need https in production, change the first line to if RACK_ENV == 'production'.).

Wrap up

Really, the overall process is quite logical. It’s a matter of making sure your DNS is set up properly so the cert request can be verified, paying for a dyno, triggering the ACM request, it generating a cert for you, and then providing a redirect to https for all http requests.

In any case, that’s all there is to it. Once your cert verifies, you should be able to push your app to heroku via git push heroku master and see it working in secure https glory at the domain you’ve configured.

Happy https-ing.

dev ruby padrino devops security