fix(pki): Azure DNS ACME challenge TXT record always written at zone apex instead of per-identifier name
Bug Report: External ACME CA — Azure DNS writes challenge TXT at zone apex instead of per-identifier name
Summary
When using an external ACME-compatible CA (e.g. Let's Encrypt) with Azure DNS as the DNS-01 provider, Infisical always creates the challenge TXT record at the zone apex (_acme-challenge relative to the configured zone) instead of at the per-identifier name _acme-challenge.<identifier>. Domain validation fails unless the requested certificate's identifier happens to be exactly the zone name.
Affected code
backend/src/services/certificate-authority/acme/acme-certificate-authority-fns.ts, function getAcmeChallengeRecord:
if (provider === AcmeDnsProvider.DNSMadeEasy || provider === AcmeDnsProvider.AzureDNS) {
// For DNS Made Easy, we don't need to provide the domain name in the record name.
recordName = "_acme-challenge";
} else {
recordName = `_acme-challenge.${identifierValue}`;
}
if (provider !== AcmeDnsProvider.DNSMadeEasy) {
recordName = await resolveAcmeChallengeCname(recordName);
}Problems for Azure DNS:
recordName = "_acme-challenge"omits the identifier entirely, so the record is written to<zone>/TXT/_acme-challengeregardless of which host is being validated.- The CNAME resolution afterwards operates on the bare
_acme-challengelabel, which can never resolve, so the documented CNAME-delegation workflow (https://infisical.com/docs/documentation/platform/pki/ca/acme-ca, FAQ "How do I use DNS CNAME delegation for ACME challenges?") cannot work with Azure DNS. - Because the record set is fixed at the apex, concurrent orders and multi-SAN orders collide: a second challenge overwrites the first (single PUT per record set), so only one identifier can ever validate.
Note: DNS Made Easy's API apparently expects zone-relative record names via a separate mechanism, but the Azure DNS provider implementation (dns-providers/azure-dns.ts) uses PUT .../dnsZones/{zone}/TXT/{recordName} — the record name must be relative to the zone and contain the identifier, otherwise the ACME CA (which queries _acme-challenge.<identifier>) cannot find the record.
Reproduction
- Create an external ACME CA (Let's Encrypt) with provider
azure-dnsand hosted zone/subscriptions/.../dnsZones/acme.example.com. - Create a CNAME in the domain's parent zone:
_acme-challenge.host.example.com → _acme-challenge.host.acme.example.com. - Request a certificate for
host.example.com. - Observed: TXT record created at
_acme-challenge.acme.example.com(zone apex), the upstream CA authorization forhost.example.comfails withDNS problem.
Expected behavior
For Azure DNS the TXT record should be created at the record name derived from the identifier (_acme-challenge.<identifierValue>), with the zone name stripped to make it relative to the configured zone, and after following any CNAME chain (resolveAcmeChallengeCname on the full record name). Then:
- per-host certificates work,
- the documented CNAME-delegation workflow works,
- concurrent/multi-SAN orders write to distinct record sets.
Suggested fix
if (provider === AcmeDnsProvider.DNSMadeEasy) {
recordName = "_acme-challenge"; // unchanged, provider API is zone-relative
} else {
recordName = `_acme-challenge.${identifierValue}`;
}
// ... CNAME resolution for all providers ...
// Then, for the Azure DNS API call, make recordName relative to the configured zone:
// strip the trailing `.${zoneName}` from recordName before PUT .../dnsZones/{zone}/TXT/{recordName}(Zone name can be derived from the hostedZoneId resource URI, whose last segment is the zone name.)
Environment
Observed on self-hosted v0.160.10; code unchanged on current main (verified via GitHub).
Source: Infisical/infisical