Protecting your Rails apps with Cloudflare Access

No one loves VPNs, especially in a lockdown

Whatever you feel about working remotely, I am pretty sure you don't love VPNs. Setting them up is tricky, using them can be clunky and they always seem to get in the way of your Zoom calls. On top of that, distributing VPN access keys to everyone in the company, when you don't see them face to face can be a challenge of its own.

Moving away from VPN to Cloudflare Access

We use a bunch of internal tools, mostly written in Rails to help and support our customers. Until now, access to those internal tools and APIs were protected through a VPN. Last week we turn off the last of our VPNs and switched over to using Cloudflare Access.

Since Google talked about their Beyond Corp concept, a VPN free system for Googlers to access their internal systems, many companies have tried to sell that as a service, including Google Cloud themselves. I've been observing these tools and trying them out, but never found that was suitable for our needs. Many of those services are SAML, SSO providers: they wrap the SaaS tools you use into a single dashboard and access control for your team. While useful, we were looking for a product that wraps our internal tools, written by our own team in better protection. I think we found the answer in Cloudflare Access.

How does Cloudflare Access Work?

Let's say you want to protect your app at https://tool.acme.corp

Using a VPN you'd configure your servers' firewalls to accept traffic only from your VPN or better still, create a VPN tunnel (or VPN termination point) to your servers without exposing them directly on the public internet.

Cloudflare Access does the same thing, but without the need for the client side of the VPN. This, combined with Cloudflare DNS is a powerful system to protect your internal assets without using a VPN.

To protect tool.acme.corp with Cloudflare Access, you need to use Cloudflare DNS for acme.corp. When someone in your team tries to access tool.acme.corp from anywhere, Cloudflare redirects them to an authentication portal, where they are authenticated using your preferred method, like Google, Github or email based one time passwords. Once authenticated, all traffic from the client goes through Cloudflare servers and gets a JWT token attached to it. This token is then picked up by the Cloudflare agent sitting on your servers. The agent authenticates and authorizes the traffic using the token and redirects the traffic to your web server, serving your internal application, which is on the same server.

Protecting a Rails app with Cloudflare Access

In this example, I'm going to protect a sample Rails application with Cloudflare Access. My Rails application uses Devise which is a common Rails authentication gem.

What you need:

  1. A Cloudflare account with Cloudflare Access
  2. A domain using Cloudflare DNS
  3. Your Rails application running on a server (you can use Cloud 66 for Rails to host it on any cloud).

Step 1: Run the Cloudflare Agent

If you have access to your servers, follow the steps on installing and authenticating the Cloudflare agent cloudflared on your server:

First, download the agent on the server.

Login

Open the browser and go to the link on your screen and authenticate yourself with Cloudflare.

Register

Now you assuming your Rails application is running on port 3000 on the server, run the following, replacing the URL with the one you'd like to use:

cloudflared tunnel --hostname tool.acme.corp http://localhost:3000

This will register tool.acme.corp with Cloudflare DNS automatically.

Now you can head to Cloudflare Access dashboard and create a new App with the tool.acme.corp endpoint and grant access to the right team members.

At this point, you should be able to access your app through tool.acme.corp URL. Next, we're going to add a Devise strategy to authenticate the Cloudflare traffic.

Step 2: A Devise Strategy for Cloudflare Access

If you use Devise, you can use this Strategy to authenticate Cloudflare Access's JWT tokens as well as pick up the username of the authorized user from the token so you don't need to authenticate them again or grant them the right access rights.

class CfAccessAuthenticatable < ::Devise::Strategies::Authenticatable
    def authenticate!
        unless request.headers["Cf-Access-Jwt-Assertion"].present?
            Rails.logger.info("JWT header not found")
            redirect!('https://www.acme.corp')
        end

        token = request.headers["Cf-Access-Jwt-Assertion"]

        payload, _ = JWT.decode(token, nil, true, {
            nbf_leeway: 30, # allowed drift in seconds
            exp_leeway: 30, # allowed drift in seconds
            iss: ENV["JWT_ISS"],
            verify_iss: true,
            aud: ENV["JWT_AUD"],
            verify_aud: true,
            verify_iat: true,
            algorithm: 'RS256',
            jwks: ->(options) do
                @cached_keys = nil if options[:invalidate]
                @cached_keys ||= keys
            end })

        email = payload["email"]

        resource = User.find_by(email: email)
        unless resource
            Rails.logger.info("User #{email} not found")
            redirect!('https://www.acme.corp')
        end

        remember_me(resource)
        resource.after_database_authentication
        success!(resource)
    rescue JWT::DecodeError => exc
        Rails.logger.error(exc.message)
        redirect!('https://www.acme.corp')
    end

    private

    def keys
        @keys ||= HTTParty.get(ENV["JWT_CERTS"]).deep_symbolize_keys!
    end

end

Now open your devise.rb file and add the following line: Warden::Strategies.add(:cf_jwt, Devise::Strategies::CfAccessAuthenticatable)

Make sure this Strategy is added to the list of Devise Strategies:

config.warden do |manager|
    manager.default_strategies(:scope => :user).unshift :cf_jwt
end

As you can see, the Strategy requires some other gems and environment variables: HTTParty and JWT gems should be added to your Gemfile.

As for the environment variables, you can find them on your Cloudflare Access dashboard for the Application you created.

Summary

Cloudflare Access is a very good way to protect your internal apps without a VPN. It can be used to protect your servers for SSH access and other services (like database servers etc) as well.

Recently published:

Try Cloud 66 for Free, No credit card required