Skip to content

infra: stop hosting usnewsmap.com in Azure DNS (#944) - #1043

Open
tgoodyear wants to merge 1 commit into
mainfrom
dns-zone-unhosted
Open

tgoodyear wants to merge 1 commit into
mainfrom
dns-zone-unhosted

Conversation

@tgoodyear

Copy link
Copy Markdown
Owner

Part of #944.

This changes the usnm-prod stack. Provision it only after the #283 index session reports "done". Until the zone is gone, every prod provision of this commit needs the guard override below, so merging earlier would block the #283 provisions.

What changes

  • New parameter dnsZoneHosted (USNM_DNS_ZONE_HOSTED), default true, so dev and new environments behave as before. It gates only module dns and the NAME_SERVERS output.
  • dnsZoneName (USNM_DNS_ZONE) stays usnewsmap.com. It still drives customDomains, the CORS origins, the managed certificates (siteCertificateId, wwwCertificateId, apiCertificateId), the availability tests and the SITE_URL/API_URL outputs.
  • infra/env/prod.json sets "USNM_DNS_ZONE_HOSTED": false. Prod's non-secret settings live in that file (USNM_DNS_ZONE is already there; scripts/lib/env.sh reads public keys from it), so the new key goes there, not in the settings store. The schema gets the key as a public boolean.
  • scripts/azure/bootstrap.sh: with the zone not hosted, the custom-domain step skips the "delegated to Azure DNS?" check (it would always fail now and print an empty NAME_SERVERS) and the _dnsauth delete in the Azure zone. The certificate ids are already recorded, so binding stays a no-op.
  • infra/README.md: settings table row, and a note in the Domain section on the Cloudflare setup.

What the next prod provision does with the zone

az stack sub show -n usnm-prod (read only) shows actionOnUnmanage: resources delete, resourceGroups detach, managementGroups detach, deny settings denyDelete. scripts/lib/env.sh creates the stack with --action-on-unmanage deleteResources, so the provision deletes the zone usnewsmap.com and its 10 managed record sets (A, CAA, CNAME www/api, TXT @/_dmarc/*._domainkey/asuid/asuid.www/asuid.api). rg-usnm-prod itself stays in the template. A deleted resource takes its deny assignment with it, so no manual az network dns zone delete is needed afterwards. A manual delete is needed only if the provision ends with the zone detached rather than deleted (check below).

The rename guard (scripts/lib/rename_guard.py) blocks this because microsoft.network/dnszones is a protected type and what-if reports the undeclared zone as Ignore. Its documented override (ADR-0017, infra/README.md "Protected-resource gate") is --allow-rename=<exact-old-name> on provision.sh, here --allow-rename=usnewsmap.com. The override accepts only an Ignore or Delete row; a missing or failed what-if still blocks. Record sets are not protected types and need no flag.

Provision procedure

After #283's "done" and this PR merged:

cd /path/to/usnewsmap.com && git switch main && git pull --ff-only
dig +short NS usnewsmap.com @8.8.8.8          # darl/nicole.ns.cloudflare.com
scripts/azure/provision.sh --env prod --what-if --allow-rename=usnewsmap.com
# check: the stack preview deletes only the usnewsmap.com zone and its record sets;
# the guard passes; nothing else is deleted or detached
scripts/azure/provision.sh --env prod --allow-rename=usnewsmap.com
az network dns zone list --query "[?name=='usnewsmap.com'].id" -o tsv   # expect nothing
scripts/azure/settings.sh --env prod --key NAME_SERVERS                  # expect empty
curl -fsS -o /dev/null https://usnewsmap.com/ && curl -fsS https://api.usnewsmap.com/readyz

If the zone is still listed (detached rather than deleted), delete it by hand: az network dns zone delete -g rg-usnm-prod -n usnewsmap.com --yes. The stack no longer manages it, so no deny assignment blocks this.

Other uses of the zone

  • bootstrap.sh: the delegation check and _dnsauth delete were its only zone uses; both are now skipped when not hosted. Certificate issuance (az containerapp hostname bind) uses public DNS, not the Azure zone.
  • dnsApexTxtValues (Google site verification) feeds only module dns. The same token is already in Cloudflare's apex TXT.
  • The asuid TXT records are in Cloudflare too, as are the apex A record to the environment's static IP, the www/api CNAMEs straight to the app's FQDN, and a CAA set that includes digicert.com (checked with dig @1.1.1.1).
  • contrib-compute/infra/boinc reads the zone as existing only when the BOINC server runs in Azure. Prod's runs on-prem, so provision-boinc.sh passes no zone.

Certificate renewal

Renewal does not need Azure DNS. Microsoft Learn, Free certificate requirements: the certificate "is automatically renewed without any action from you as long as your app continues to meet the requirements". Those requirements are an A record for the apex pointing to the environment's IP, a CNAME for each subdomain "that maps directly to the container app's generated domain name" (an intermediate CNAME such as Cloudflare "blocks certificate issuance and renewal"), CAA allowing DigiCert, and the app reachable and running. The records are created "using the DNS provider that's hosting your domain", whichever that is. Cloudflare holds the apex, www and api as DNS-only records, which meets this. Turning on the proxy for any of them would break renewal.

Checks

  • ~/.azure/bin/bicep 0.48.1: build and lint of infra/main.bicep, scripts/ci/lint-bicep.sh, scripts/ci/build-bicep.sh; build-params with USNM_DNS_ZONE_HOSTED=false gives dnsZoneHosted: false with dnsZoneName: usnewsmap.com.
  • scripts/ci/check-names.py, scripts/ci/no-private-addresses.sh, scripts/lib/env_settings.py --check.
  • python3 -m unittest scripts.azure.test_rename_guard scripts.azure.test_env_scripts scripts.azure.test_resource_names scripts.ci.test_check_names: 236 tests OK.
  • Nothing was provisioned or deployed.

🤖 Generated with Claude Code

Add dnsZoneHosted (USNM_DNS_ZONE_HOSTED, default true). It gates only
the dns module and the NAME_SERVERS output; dnsZoneName still drives the
custom domains, managed certificates, CORS origins, web tests and URL
outputs. Prod's domain is at Cloudflare since 20 October, so prod.json
sets it false, and bootstrap skips the Azure DNS delegation check and
the _dnsauth cleanup when the zone isn't hosted.

Part of #944.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 23, 2026 19:10
@tgoodyear

Copy link
Copy Markdown
Owner Author

Hold: don't merge before the #283 index session's "done". On main, every prod provision fails the protected-resource guard unless it passes --allow-rename=usnewsmap.com, and that would block the rebuild's own provision. After "done": merge, then scripts/azure/provision.sh --env prod --what-if --allow-rename=usnewsmap.com, then the real provision with the same flag, as the body describes.

🤖 Generated with Claude Code

Copilot AI 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.

🔵 Needs a closer look

Production DNS deletion requires human confirmation that #283 is complete and the stack preview removes only the intended zone and records.

No open issues 🎉

What changed in this PR

Separates custom-domain configuration from Azure DNS hosting so production can retire its Azure DNS zone after the Cloudflare migration, while preserving domain bindings and certificates.

Changes:

  • Adds a DNS-hosting flag that defaults to enabled and disables it for production.
  • Gates Azure DNS deployment, name-server output, and bootstrap’s Azure-specific DNS steps.
  • Documents Cloudflare record management and managed-certificate renewal requirements.
File Description
scripts/​azure/​bootstrap.sh Skips Azure DNS delegation checks and record cleanup when externally hosted.
infra/​README.md Documents the hosting flag and Cloudflare operational requirements.
infra/​main.bicepparam Maps the environment setting to the DNS-hosting parameter.
infra/​main.bicep Conditionally deploys DNS and emits name servers.
infra/​env/​schema.json Declares the hosting setting as a public boolean.
infra/​env/​prod.json Disables Azure DNS hosting for production.

Review effort: Balanced

This branch has not been deployed

No deployments
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.

2 participants