Cloudflare Zero Trust — Evospin docs sites¶
Terraform module that provisions Cloudflare Access protection for two
documentation sites on the evo-verse.dev zone:
| Site | Hostname | Pages project | Audience | Google group |
|---|---|---|---|---|
| Team | docs.evospin.evo-verse.dev |
ebit-docs |
Developers (full docs) | evospin-dev@evo-verse.com |
| Client | platform.evo-verse.dev |
ebit-docs-client |
Clients (curated, guests) | docs-client-guests-evospin@evo-verse.com |
Zero Trust team domain: evo-verse-dev.cloudflareaccess.com
Both sites authenticate via the same Google Workspace IdP. Authorization
is by Google group membership — managed in Google Admin, not here. PR
preview deployments on *.pages.dev are gated by the same policies so
previews don't leak.
The Cloudflare Pages projects themselves are created on first
wrangler pages deploy (run from .github/workflows/deploy-docs.yml), so
this module does not manage them.
Prerequisites¶
- Pages projects
ebit-docsandebit-docs-clientexist (one workflow run creates both since the matrix builds team + client). - Cloudflare API token with these permissions:
Account → Access: Apps and Policies → EditAccount → Access: Organizations, Identity Providers, and Groups → EditZone → DNS → Edit(scoped toevo-verse.dev)- Google Cloud OAuth credentials for the Workspace IdP:
- Google Cloud Console → APIs & Services → Credentials
- OAuth client ID (type Web application)
- Authorized redirect URI:
https://evo-verse-dev.cloudflareaccess.com/cdn-cgi/access/callback - For the Workspace IdP to read group membership, the Admin SDK API must be enabled on the project.
- Two Google Workspace groups:
evospin-dev@evo-verse.comfor developersdocs-client-guests-evospin@evo-verse.comfor client guests — this group must allow external members so non-Workspace email addresses can be added.
How it runs (pipeline)¶
.github/workflows/terraform.yml drives this module:
validatejob —fmt+init -backend=false+validateon every trigger (including the test branch). No credentials, no state, so it can't touch real infrastructure. This is the schema gate.plan-applyjob —terraform planon PRs (posted as a sticky PR comment) andterraform apply -auto-approveon push tomaster.
State lives in Cloudflare R2 (see providers.tf), so apply is idempotent
across runs.
Required GitHub secrets¶
| Secret | Used as | Notes |
|---|---|---|
CLOUDFLARE_API_TOKEN |
TF_VAR_cloudflare_api_token |
Account token (Access + DNS edit). Set. |
CLOUDFLARE_ACCOUNT_ID |
TF_VAR_cloudflare_account_id |
Set. |
CLOUDFLARE_ZONE_ID |
TF_VAR_cloudflare_zone_id |
Zone ID for evo-verse.dev. |
GOOGLE_CLIENT_ID |
TF_VAR_google_client_id |
Google OAuth web client. |
GOOGLE_CLIENT_SECRET |
TF_VAR_google_client_secret |
Google OAuth web client. |
R2_ACCESS_KEY_ID |
AWS_ACCESS_KEY_ID |
R2 access key for the state bucket. |
R2_SECRET_ACCESS_KEY |
AWS_SECRET_ACCESS_KEY |
R2 secret for the state bucket. |
One-time bootstrap¶
# Create the R2 state bucket (once)
wrangler r2 bucket create evoverse-dev
# Then create an R2 API token (Object Read & Write) and add the
# access key id / secret as the R2_* GitHub secrets above.
Running locally¶
cd infra/cloudflare
cp terraform.tfvars.example terraform.tfvars # set cloudflare_zone_id
export TF_VAR_cloudflare_api_token="$(op read op://Infra/cf-docs-token/credential)"
export TF_VAR_google_client_id="..."
export TF_VAR_google_client_secret="..."
export AWS_ACCESS_KEY_ID="<r2-access-key-id>"
export AWS_SECRET_ACCESS_KEY="<r2-secret>"
terraform init
terraform plan
terraform apply
terraform apply is idempotent. Re-running reconciles drift if anyone edited
the Access app in the dashboard.
Day-2: adding/removing users¶
- Developers: add/remove from the Google Workspace group
evospin-dev@evo-verse.comin Google Admin. Effective at next login (or immediately on session expiry). - Client guests: add the external email as a member of
docs-client-guests-evospin@evo-verse.com. Workspace must be configured to allow external group members. - Revoke an active session: Cloudflare Zero Trust → Users → \
→ Revoke. Useful when offboarding before the 8h / 24h session TTL expires.
Nothing here changes when you add/remove people — it's all Google-side.
Notes & caveats¶
- I haven't been able to run
terraform validatein this environment. The resource shapes follow the documented Cloudflare provider v5 patterns, but the firstterraform planis the real test. If a resource argument has drifted in newer provider releases (Cloudflare moves fast), fix locally and PR back. - State is local by default. If more than one operator will run this,
uncomment the S3 backend block in
providers.tfand create the bucket first. - The Workspace IdP resource holds
google_client_secretin state. Don't committerraform.tfstate*; the local.gitignorealready excludes it.
Disaster recovery¶
Recreates the IdP, both groups, both apps + their preview apps, and both DNS records. RTO ~5 min once the token + Google OAuth creds are at hand.