Tag: security

  • Rails 4.1 introduced the concept of secrets.yml, a file in which you store all the credentials for your app, separated by environment, so for example, development can talk to Test Stripe and production to Live Stripe. Furthermore, this file is capable of picking up environment variables which allows you to divorce credentials from code. Not properly separating credentials from code recently cost Uber the leakage of 50,000 driver names and license numbers.

    At Qredo we are very strict about handling credentials, including the ones for SMTP, which in Rails projects are normally stored in config/environments/development.rbconfig/environments/production.rb, etc. Trying to read Rails.application.secrets from those files doesn’t work, because they are loaded before the secrets, so, we came up with this alternative solution.

    The environment files that need to use SMTP for delivering email have this common configuration:

    config.action_mailer.delivery_method = :smtp
    config.action_mailer.smtp_settings = {
      address: "smtp.example.com",
      port: 587,
      authentication: "plain",
      enable_starttls_auto: true
    }

    and then, on config/initializers/email.rb we finish configuring our SMTP credentials by reading it from secrets.yml:

    if ActionMailer::Base.delivery_method == :smtp
      ActionMailer::Base.smtp_settings[:domain]    = Rails.application.secrets.smtp_domain
      ActionMailer::Base.smtp_settings[:user_name] = Rails.application.secrets.smtp_user_name
      ActionMailer::Base.smtp_settings[:password]  = Rails.application.secrets.smtp_password
    end
    

    In config/secrets.yml you need set those credentials:

    development:
      secret_key_base: 123...
      smtp_domain: example.org
      smtp_user_name: postmaster@oderq.com
      smtp_password: <%= ENV["SMTP_PASSWORD"] %>
    
    test:
      secret_key_base: c7f3f62597b14c7287d75c239168fd3a89d3a6cb51beb909679c2609e912aaa3ca0a0aa5df2e191648baed6fe49698527a75dd265d222697576405971f478c98
    
    staging:
      secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
      smtp_domain: example.net
      smtp_user_name: staging_user@example.net
      password: <%= ENV["SMTP_PASSWORD"] %>
    
    production:
      secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
      smtp_domain: example.com
      smtp_user_name: user@example.com
      smtp_password: <%= ENV["SMTP_PASSWORD"] %>

    And that’s it! Enjoy bringing SMTP credentials into Rails >4.0 secrets management.

  • We tend to be very security conscious at Carousel Apps and one thing we often do is force all our applications to run over TLS (aka SSL), that is, when you go to http://example.com we redirect you to https://example.com. This little article will show you how to do it in a Luminus application.

    First, add Ring SSL to your project by editing project.clj , finding the list of dependencies and adding:

    [ring/ring-ssl "0.2.1"]

    That library provides various Ring middleware functions to deal with SSL. The first one you care about is wrap-ssl-redirect  that will cause an HTTP request to be redirected to the equivalent HTTPS one.

    In your middleware.clj  file, find the function wrap-base  which will look something like:

    (defn wrap-base [handler]
      (-> handler
          wrap-dev
          wrap-auth
          (wrap-idle-session-timeout
            {:timeout          (* 60 30)
             :timeout-response (redirect "/")})
          wrap-formats
          (wrap-defaults
            (-> site-defaults
                (assoc-in [:security :anti-forgery] false)
                (assoc-in [:session :store] (memory-store session/mem))))
          wrap-servlet-context
          wrap-internal-error))
    

    Depending on which options you added, you might have fewer or more middleware functions in your project. You can start simple by adding wrap-ssl-redirect  to it, like this:

    (defn wrap-base [handler]
      (-> handler
          wrap-ssl-redirect
          wrap-dev
          ...))

    Redirecting to SSL should happen as early as possible to avoid leaking any information over a non-secure channel. With that code you’ll immediately run into trouble when running your project locally, because neither your development environment nor your test one are likely to support TLS, so you’ll get redirected to HTTPS and it won’t work.

    That’s easy to solve by creating a function, let’s call it enforce-ssl , that will skip the enforcement when developing or testing:

    (defn enforce-ssl [handler]
      (if (or (env :dev) (env :test))
        handler
        (-> handler
            wrap-ssl-redirect)))

    and then in your middleware configuration:

    (defn wrap-base [handler]
      (-> handler
          enforce-ssl
          wrap-dev
          ...))

    The function wrap-ssl-redirect obviously checks whether you are already accessing the site over SSL and if that’s the case, it doesn’t redirect you; otherwise you’d have a redirect loop.

    If your application is sitting behind a proxy or a load balancer, like in the case of Heroku, Linode’s Load Balance, AWS Elastic Load Balancing, etc. the proxy will be terminating the HTTPS connection and then it’ll issue an HTTP (non-S) connection to your application because since now you are in an internal secure network, encryption is no longer required and HTTP is faster. Thus, your application after forwarding to HTTPS will still receive connections to HTTP and forward again and it’ll be stuck in an infinite forwarding loop.

    The convention for this situation is that those proxies will add the header X-Forwarded-Proto with the original protocol, either http  or https. There’s another middleware function called wrap-forwarded-scheme  that will look at X-Forwarded-Proto (or another header entry of your choosing) and set that as the actual scheme in the request so a simple check for http  or https would suffice:

    (defn enforce-ssl [handler]
      (if (or (env :dev) (env :test))
        handler
        (-> handler
            wrap-ssl-redirect
            wrap-forwarded-scheme)))

    Doing the scheme forwarding should be the first thing you do, so that wrap-ssl-redirect  can use the correct scheme.

    IMPORTANT: if your application is not behind a trusted proxy, or the proxy is using a different header, then blindly applying wrap-forwarded-scheme would be dangerous as your application could be easily deceived into believing a connection is secure which would enable a man‐in‐the‐middle attack.

    One last thing that we do is add wrap-hsts  which makes sure the browser from now on will only use HTTPS for this domain, even if the user types http (which would result in a redirect anyway). The final code looks like this:

    (defn enforce-ssl [handler]
      (if (or (env :dev) (env :test))
        handler
        (-> handler
            wrap-hsts
            wrap-ssl-redirect
            wrap-forwarded-scheme)))

    And that’s it, your application now uses TLS/SSL by default, something that you should always consider and definitely do if there’s any kind of auth at all.

  • There’s a gem called bundler-audit that checks whether any of the gems in your project have open security advisors against them. A year or so ago there was an infamous month in which Rails itself got three of those. It was terrible and I think bundler-audit is a good idea. My only problem with it is having to remember to run it: it just won’t happen. I need to run it automatically and an easy way to do that is to run it as part of my tests.

    Unfortunately, bundler-audit doesn’t make it easy. It’s designed for the command line and that’s it, but these days it’s easier than a year ago and I recommend everybody to add them to their integration tests. Here’s how we do it at Watu:

    require "test_helper"
    require "bundler/audit/database"
    require "bundler/audit/scanner"
    
    class SecurityTest < ActionDispatch::IntegrationTest
      should "not have any vulnerable gems" do
        Bundler::Audit::Database.update!
        scanner = Bundler::Audit::Scanner.new
        scanner.scan do
          raise "There are vulnerable gems in your Gemfile. Run bundle-audit check to know more"
        end
      end
    end
    

    I don’t try to show the vulnerable gems because I found those methods to not be easily reusable and I didn’t want to copy them because they look like they might change at any moment. It’s not a big problem, if something is wrong, you should run bundle-audit check anyway.