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:
- A Cloudflare account with Cloudflare Access
- A domain using Cloudflare DNS
- 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.