Easily secure your non-prod environments from external users for free
If you have a private domain sitting on the public internet, chances are anyone can reach it. Search engines index it, curious people poke at it, and bots scan it all day long. The usual fixes are a VPN (complex and annoying to maintain), IP allowlists (break the moment someone works from a coffee shop), or basic auth (everyone shares the same password forever). However, the good thing is that there's another way.
Cloudflare Access
The idea is simple, your DNS record for the environment must be proxied
through Cloudflare (the orange cloud), so every request passes through their
edge. Then you create a self-hosted
Cloudflare Access application
pointing to that domain, and attach a
policy
to it, for example, allowing only emails ending in
@mycompany.com or a specific list of addresses.
From that point on, anyone hitting the gated address gets redirected to a login page, with an input to enter an email. Once the email is submitted, Cloudflare sends a one-time PIN to it, if it matches the conditions in the policy, then the user gets a session cookie and goes through. If the email doesn't match, the user never touches your servers.
Code example
You can click this together on the Cloudflare dashboard, but you can also do it through Terraform with two resources:
resource "cloudflare_zero_trust_access_application" "development" {
account_id = local.cloudflare_account_id
name = "development.${var.project_name}"
domain = "development.${var.apex_domain_name}"
type = "self_hosted"
session_duration = "12h"
http_only_cookie_attribute = true
app_launcher_visible = false
policies = [{
id = cloudflare_zero_trust_access_policy.default.id
precedence = 1
}]
}
resource "cloudflare_zero_trust_access_policy" "default" {
account_id = local.cloudflare_account_id
name = "Default"
decision = "allow"
session_duration = "24h"
include = [{
email = {
email = local.default_email
}
}]
}
Worth noting
-
session_durationcontrols how long the cookie is valid, so people are not logging in on every request; -
http_only_cookie_attributekeeps the session cookie away from JavaScript; -
app_launcher_visiblehides the app from Cloudflare's app launcher page, since this is just an internal environment; -
The policy is separate from the application, so you can reuse the same
defaultpolicy across multiple apps.
Do you need to protect staging and QA too? Duplicate the application resource
(or use for_each) pointing to each subdomain, and
reference the same policy.
Costs
This is the best part, it's free. Cloudflare's Zero Trust free tier covers up to 50 users, which is plenty for protecting internal environments of most teams.
There is only one catch (of course there would be a catch). The free
Universal SSL
certificate
covers the root domain and one level of subdomain. So development.myproject.com,
potatochips.myproject.com and
whatever.myproject.com are all fine. But if you go one
level deeper, like api.development.myproject.com, that's a
second-level subdomain and the free certificate won't cover it, so you'd need
Cloudflare's paid
Advanced Certificate Manager
for that.
Wrap-up
For such a simple setup and a proxied DNS record, your non-prod environments stop being public. No VPN, no shared passwords, no drastic changes, just an email check at the edge. It's one of those rare cases where it seems too good to be true, but it actually is pretty good.