71 lines
3.1 KiB
Markdown
71 lines
3.1 KiB
Markdown
# Cloudflare Access (Zero Trust) Setup
|
|
|
|
## Overview
|
|
Cloudflare Access sits in front of subdomains and checks user identity before traffic reaches the origin server. Provides SSO and 2FA for any self-hosted service without server-side auth code.
|
|
|
|
## When to Use
|
|
- Replace Caddy basic_auth for internal pages
|
|
- Add 2FA layer over existing login systems (TwentyCRM magic link, DocuSeal)
|
|
- Unify auth across multiple services (CRM, internal dashboard, DocuSeal)
|
|
- Protect any self-hosted web app without modifying the app itself
|
|
|
|
## Prerequisites
|
|
- Cloudflare API token with **Access: Apps and Policies: Edit** permission
|
|
- Existing DNS-only token can be updated (Edit → Add permission, then regenerate)
|
|
- Subdomains must be proxied through Cloudflare (orange cloud) for Access to work
|
|
|
|
## Setup via API
|
|
|
|
### Account
|
|
- Account ID: from `GET /client/v4/accounts`
|
|
- Token: with Access: Apps and Policies: Edit permission
|
|
|
|
### Create an Access Application
|
|
```bash
|
|
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name":"App Name","domain":"subdomain.yourdomain.com","type":"self_hosted","session_duration":"24h"}'
|
|
```
|
|
|
|
### Add a Policy to an Application
|
|
```bash
|
|
APP_ID=$(curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps" ... | jq '.result[] | select(.name=="App Name").id')
|
|
|
|
# Policy with email domain include (no 2FA requirement yet)
|
|
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCT/access/apps/$APP_ID/policies" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name":"Team","decision":"allow","include":[{"email_domain":{"domain":"germainebrown.com"}}]}'
|
|
```
|
|
|
|
### Policy Types
|
|
- `include` — who is ALLOWED (email, email_domain, everyone, etc.)
|
|
- `require` — what is REQUIRED (totp, mfa, etc.)
|
|
- `exclude` — who is BLOCKED
|
|
|
|
### Known Issues
|
|
- `require: [{totp: {}}]` returned `invalid 'require' configuration` in testing (Jul 7, 2026). May need to configure 2FA at the Zero Trust org level first, or use the dashboard UI.
|
|
- 2FA/one-time passcode is a "require" rule but may need to be set via the Cloudflare dashboard rather than API for initial setup.
|
|
- Session duration of "24h" means users re-authenticate daily. Adjust based on security needs.
|
|
|
|
## Dashboard Setup (Fallback)
|
|
If API fails:
|
|
1. Login at https://one.dash.cloudflare.com/
|
|
2. Access → Applications → Add application → Self-hosted
|
|
3. Enter subdomain, session duration
|
|
4. Add policy: name, include (email or email_domain), require (2FA if needed)
|
|
5. Save
|
|
|
|
## DNS Requirement
|
|
- Subdomains covered by Access must be proxied (orange cloud) in Cloudflare DNS
|
|
- This enables Cloudflare to intercept requests before they reach the origin
|
|
- Non-proxied (gray cloud) records bypass Access entirely
|
|
|
|
## Removing Caddy Basic Auth After Access is Working
|
|
Once Access is confirmed working, remove the `basic_auth` block from the Caddyfile or the old auth section:
|
|
```bash
|
|
# In Caddyfile: remove @internal handle block and basic_auth
|
|
# Reload: caddy reload --config /etc/caddy/Caddyfile
|
|
```
|