Skip to content

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

  1. Pages projects ebit-docs and ebit-docs-client exist (one workflow run creates both since the matrix builds team + client).
  2. Cloudflare API token with these permissions:
  3. Account → Access: Apps and Policies → Edit
  4. Account → Access: Organizations, Identity Providers, and Groups → Edit
  5. Zone → DNS → Edit (scoped to evo-verse.dev)
  6. Google Cloud OAuth credentials for the Workspace IdP:
  7. Google Cloud Console → APIs & Services → Credentials
  8. OAuth client ID (type Web application)
  9. Authorized redirect URI: https://evo-verse-dev.cloudflareaccess.com/cdn-cgi/access/callback
  10. For the Workspace IdP to read group membership, the Admin SDK API must be enabled on the project.
  11. Two Google Workspace groups:
  12. evospin-dev@evo-verse.com for developers
  13. docs-client-guests-evospin@evo-verse.com for 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:

  • validate jobfmt + init -backend=false + validate on every trigger (including the test branch). No credentials, no state, so it can't touch real infrastructure. This is the schema gate.
  • plan-apply jobterraform plan on PRs (posted as a sticky PR comment) and terraform apply -auto-approve on push to master.

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.com in 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 validate in this environment. The resource shapes follow the documented Cloudflare provider v5 patterns, but the first terraform plan is 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.tf and create the bucket first.
  • The Workspace IdP resource holds google_client_secret in state. Don't commit terraform.tfstate*; the local .gitignore already excludes it.

Disaster recovery

terraform init
terraform apply

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.