Using Cloudflare Access and devise together.
Cloudflare Access is my current go-to zero trust platform for securing internal applications that require access outside of our closed networks. Cloudflare supports effectively a reverse tunnel through their Argo offering so you don't have to open any external firewall ports. This is fine and good, but following the zero trust mantra I wanted to take things a step further and also verify the JWT provided as a cookie and in the response headers of all traffic that has passed through teams/access and been verified according to the rule set configured.
This seemed simple on the surface. A little googling pointed me to an article written by Khash over at cloud66
Everything below assumes you have CloudFlare Access working on your application up to the point of verifying the JWT provided.
The first issue I ran into was the application in question was running a older version of jwt/ruby-jwt. I kept getting the following error
JWT header not found
Nil JSON web token
Turns out jwks are not supported fully in older versions. So ensure that you have at least V2.2.3
Dependencies
I used cloud66's example devise strategy as a starting place and started digging through all the cloudflare config to extract the necessary components. In the end, you only need two things:
Gather Required Information
- the audience tag is located on the "overview tab of the application in Cloudflare teams.
2. The Cloudflare Access domain. This will be something like https://acmeinc.cloudflareaccess.com
Configure Secrets
You will need to add both elements listed above ether to your rails secrets or into environment variables.
production:
CF_JWT_AUD: ACME_SUPER_SECRET_AUD_TAG
CF_TEAMS_URL: "https://acmeinc.cloudflareaccess.com"
Devise Strategy
I had trouble getting the few examples I found online to work, so I ended up adopting a combination of the cloud66 example mashed with the ruby-jwt's examples to get a fully functional devise strategy. A few key modifications to the Cloud66 example are to imply the public key from cloudflares public facing trusted site and to also use that same URL to verify the ISS field in the JWT. The URL is provided by the token, and also from the servers configuration, and this way ensures that both are the same. The second change was to move the lambda outside of the JWT decode for readability.
Next you will need to modify the devise initializer to include the new strategy.
At the top of devise.rb
Warden::Strategies.add(:cf_jwt, Devise::Strategies::CfAccessAuthenticatable)
then within the setup block
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :cf_jwt
end
In the end your file should look something like this:
In closing, you should have your standard devise login, along with jwt validation of the users true ( as it can be ) origin. Remaining things to do is to patch X forwarded IP to correctly reflect the other end of the cloudflare tunnel. Stay tuned.