Skip to content

Add Cloudflare DNS and Turnstile to Terraform - #623

Merged
thomasluizon merged 2 commits into
mainfrom
feature/ticket-804-cloudflare-dns
Sep 27, 2026
Merged

thomasluizon merged 2 commits into
mainfrom
feature/ticket-804-cloudflare-dns

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Closes thomasluizon/orbit-tickets#804

Change

  • Pinned Cloudflare provider 5.26.0 in infra/versions.tf and added the full useorbit.org zone and explicit Free subscription in infra/cloudflare.tf.
  • Recreated all 16 existing DNS records with automatic TTL and proxying disabled. The apex, www, and app targets are variables with the current values as defaults. Staging CNAMEs derive their targets from the Render staging services.
  • Added one managed Turnstile widget for the four specified hostnames. Its secret flows to production and staging SecureString parameters and then to the API environment groups in infra/configuration.tf. The public site key and assigned nameservers are Terraform outputs. No enforcement setting changes.
  • Updated infra/README.md and added infra/check-dns-cutover.sh to compare DNS answers after the registrar switch. The provider lock file records the exact plugin build.

The existing records share one map so their TTL and proxy policy is uniform. The staging records use the Render resource URLs so their assigned hostnames do not have to be guessed.

Verification

  • terraform fmt -check -recursive infra: passed.
  • terraform -chdir=infra validate -no-color: passed.
  • Evaluated local.existing_dns_records through terraform console; all 16 entries matched dig @launch1.spaceship.net answers, including MX priorities and the Google DKIM value.
  • bash infra/check-dns-cutover.sh launch1.spaceship.net: passed as a script check. The Cloudflare comparison runs after apply and the nameserver switch.
  • dotnet build Orbit.slnx: 0 errors.
  • dotnet test Orbit.slnx --no-build --verbosity quiet: 6,756 passed, 0 failed.

External interface evidence

  • The installed Cloudflare 5.26.0 schema from terraform -chdir=infra providers schema -json exposes cloudflare_zone.id and name_servers, cloudflare_turnstile_widget.sitekey and sensitive secret, and cloudflare_zone_subscription.rate_plan.id with free as an allowed value. It defines DNS TTL 1 as automatic. The zone and widget do not exist yet, so there is no live resource response to inspect before apply.
  • The installed AWS 6.66.0 schema marks aws_ssm_parameter.value sensitive. The installed Render 1.9.1 schema marks render_env_group.env_vars.value sensitive and exposes computed render_web_service.url.
  • terraform -chdir=infra show -json | jq -r '.values.root_module.resources[] | select(.address == "render_web_service.staging_api") | .values.url' returned https://orbit-api-staging-uqu2.onrender.com. The staging web service is not in state yet; it uses the same Render resource schema.

Assumptions

  • Used a single record map for the 16 existing entries; rejected separate resource blocks because every entry shares the same TTL and proxy policy.
  • Derived staging CNAME targets from the Render service URL host; rejected constructing hostnames from service names because Render assigns a suffix.
  • Declared the Free zone subscription explicitly; rejected relying on the zone creation default because the requested plan should appear in Terraform.

Manual steps

  • In the operator shell, set CLOUDFLARE_API_TOKEN from the macOS Keychain item with service orbit-cloudflare-api-token. A successful terraform -chdir=infra plan -var-file=local.tfvars proves the provider can authenticate. The existing RENDER_API_KEY, AWS default credentials, and published image digests in infra/local.tfvars are also required for that plan.
  • In the operator shell, apply the approved Terraform plan. Confirm the cloudflare_name_servers and turnstile_site_key outputs and the two BotProtection__SecretKey SecureString names in AWS Systems Manager Parameter Store for us-east-2. Confirm both API environment groups in the Render Dashboard receive the key.
  • In Spaceship Advanced DNS for useorbit.org, open Nameservers, choose Change and Custom nameservers, and enter both cloudflare_name_servers values. After propagation, dig NS useorbit.org must show the assigned nameservers and bash infra/check-dns-cutover.sh <cloudflare-nameserver> must pass for each one.

@thomasluizon

Copy link
Copy Markdown
Owner Author

Orchestrator terraform plan against the real S3 state, Render workspace and Cloudflare account at head 661b6b2 (web digests set to a placeholder for the plan only; the two web services and their links are applied once the first image exists):

# aws_ssm_parameter.turnstile_secret["production"] will be created
# aws_ssm_parameter.turnstile_secret["staging"] will be created
# cloudflare_dns_record.existing["apex_a"] will be created
# cloudflare_dns_record.existing["apex_mx"] will be created
# cloudflare_dns_record.existing["api_cname"] will be created
# cloudflare_dns_record.existing["app_cname"] will be created
# cloudflare_dns_record.existing["dmarc_txt"] will be created
# cloudflare_dns_record.existing["google_dkim_txt"] will be created
# cloudflare_dns_record.existing["google_spf_txt"] will be created
# cloudflare_dns_record.existing["google_verification_4ia_txt"] will be created
# cloudflare_dns_record.existing["google_verification_be2_txt"] will be created
# cloudflare_dns_record.existing["resend_send_dkim_txt"] will be created
# cloudflare_dns_record.existing["resend_send_spf_txt"] will be created
# cloudflare_dns_record.existing["resend_updates_dkim_txt"] will be created
# cloudflare_dns_record.existing["resend_updates_spf_txt"] will be created
# cloudflare_dns_record.existing["send_send_mx"] will be created
# cloudflare_dns_record.existing["send_updates_mx"] will be created
# cloudflare_dns_record.existing["www_cname"] will be created
# cloudflare_dns_record.staging["api_staging"] will be created
# cloudflare_dns_record.staging["staging"] will be created
# cloudflare_turnstile_widget.orbit will be created
# cloudflare_zone.orbit will be created
# cloudflare_zone_subscription.orbit will be created
# render_env_group.production_api will be updated in-place
# render_env_group.staging_api will be updated in-place
# render_env_group_link.production_api will be updated in-place
# render_env_group_link.production_web will be created
# render_env_group_link.staging_api will be updated in-place
# render_env_group_link.staging_web will be created
# render_web_service.production_web will be created
# render_web_service.staging_web will be created
Plan: 27 to add, 4 to change, 0 to destroy.

The env group updates add only BotProtection__SecretKey from the new SSM parameters; BotProtection__Enabled is unchanged. Nothing is destroyed.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Verify the new authoritative DNS answers before changing the registrar delegation; the current sequence discovers missing or incorrect records only after they affect live traffic.

Reviewed changes Terraform provisioning and cutover guidance for Cloudflare DNS and Turnstile:

  • DNS zone and records: Adds a full Cloudflare zone, Free subscription, existing DNS records, and staging Render CNAMEs.
  • Turnstile delivery: Creates a widget and passes its secret through SSM to both API environment groups, with nameserver and site-key outputs.
  • Cutover tooling: Documents deployment and adds a script comparing existing Spaceship DNS answers with Cloudflare answers.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using GPT Sol | 𝕏

Comment thread infra/README.md Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes Re-reviewed the DNS cutover instructions changed since the prior Pullfrog review.

  • Pre-delegation verification: Moved the DNS comparison ahead of the registrar switch, requiring both assigned Cloudflare nameservers to match Spaceship before delegation and checking again after propagation.

Pullfrog  | View workflow run | Using GPT Sol | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit 7e946a6 into main Sep 27, 2026
26 checks passed
@thomasluizon
thomasluizon deleted the feature/ticket-804-cloudflare-dns branch September 27, 2026 22:51
thomasluizon added a commit that referenced this pull request Sep 28, 2026
)

* Add Render Terraform for production and staging (#621)

* Add Terraform for Render production and staging

* Handle Render database URLs without explicit ports

* Keep production intact through the first apply and isolate staging integrations

The imported API service ignores its own env_vars so the first apply only
adds and links the environment group, and the existing Render project is
imported (environments keyed by their live names) so the API stays in its
Production environment while a Staging environment is added. Staging gets
placeholder Stripe identifiers, staging return URLs, its own redirect
allowlist and an invalid Supabase host, so it cannot touch production
billing or storage. Empty custom domain lists become null, the landing no
longer auto-deploys, and the web health check uses /api/health.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1480d1b)

* Store confirmed waitlist contacts in Orbit database (#620)

* feat: store confirmed waitlist marketing contacts locally

* fix: preserve marketing opt-outs and canonicalize waitlist languages

* Restore user marketing delivery after explicit opt-in

(cherry picked from commit 4c7b76a)

* Add Google authorization code sign in (#622)

* Implement Google authorization code sign in for ticket 796

* Register Google code routes in agent catalog

* Parse Google token responses with installed SDK

* fix: retry Google code sign-in after redemption

(cherry picked from commit 2301ccc)

* Add Cloudflare DNS and Turnstile to Terraform (#623)

* Add Cloudflare DNS and Turnstile Terraform resources

* Verify Cloudflare DNS answers before the registrar switch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7e946a6)

* Gate production API releases through Render (#624)

* Add gated production API release workflow

* Handle queued Render deploys and recheck the live commit before recording a release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 78b42f2)

* Keep Terraform from reverting web release digests (#627)

* fix: preserve workflow deployed web digests in Terraform

* docs: guard web service applies against Render image tag bug

(cherry picked from commit b8ea2c1)

* Configure staging billing from SSM and verify test purchases (#629)

(cherry picked from commit 5e5623e)

* Align Google code error assertion with redesign response

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant