Securing Padrino apps with https and ssl on Heroku
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
# 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
🚀 ~ 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
🚀 ~ heroku domains --app your_app_name
If it comes up looking something like this with the herokudns.com,
=== 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.
🚀 ~ 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:
🚀 ~ 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,
=== 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):
# 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.