Repository navigation
Conversation
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>
|
Hold: don't merge before the #283 index session's "done". On main, every prod provision fails the protected-resource guard unless it passes 🤖 Generated with Claude Code |
There was a problem hiding this comment.
🔵 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
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
dnsZoneHosted(USNM_DNS_ZONE_HOSTED), defaulttrue, so dev and new environments behave as before. It gates onlymodule dnsand theNAME_SERVERSoutput.dnsZoneName(USNM_DNS_ZONE) staysusnewsmap.com. It still drivescustomDomains, the CORS origins, the managed certificates (siteCertificateId,wwwCertificateId,apiCertificateId), the availability tests and theSITE_URL/API_URLoutputs.infra/env/prod.jsonsets"USNM_DNS_ZONE_HOSTED": false. Prod's non-secret settings live in that file (USNM_DNS_ZONEis already there;scripts/lib/env.shreads 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 emptyNAME_SERVERS) and the_dnsauthdelete 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) showsactionOnUnmanage: resourcesdelete, resourceGroupsdetach, managementGroupsdetach, deny settingsdenyDelete.scripts/lib/env.shcreates the stack with--action-on-unmanage deleteResources, so the provision deletes the zoneusnewsmap.comand its 10 managed record sets (A, CAA, CNAME www/api, TXT @/_dmarc/*._domainkey/asuid/asuid.www/asuid.api).rg-usnm-proditself stays in the template. A deleted resource takes its deny assignment with it, so no manualaz network dns zone deleteis 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 becausemicrosoft.network/dnszonesis a protected type and what-if reports the undeclared zone asIgnore. Its documented override (ADR-0017, infra/README.md "Protected-resource gate") is--allow-rename=<exact-old-name>onprovision.sh, here--allow-rename=usnewsmap.com. The override accepts only anIgnoreorDeleterow; 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:
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_dnsauthdelete 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 onlymodule dns. The same token is already in Cloudflare's apex TXT.asuidTXT records are in Cloudflare too, as are the apex A record to the environment's static IP, thewww/apiCNAMEs straight to the app's FQDN, and a CAA set that includesdigicert.com(checked withdig @1.1.1.1).contrib-compute/infra/boincreads the zone asexistingonly when the BOINC server runs in Azure. Prod's runs on-prem, soprovision-boinc.shpasses 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/bicep0.48.1: build and lint ofinfra/main.bicep,scripts/ci/lint-bicep.sh,scripts/ci/build-bicep.sh;build-paramswithUSNM_DNS_ZONE_HOSTED=falsegivesdnsZoneHosted: falsewithdnsZoneName: 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.🤖 Generated with Claude Code