Symptom
PerformDcvIfNeededAsync (CERTInextCAPlugin.cs:1442-1704) built the challenge hostname directly from DcvTxtRecordTemplate against the raw enrollment domain and resolved the DNS provider against that same raw domain:
var validator = DomainValidatorFactory.ResolveDomainValidator(domain, "dns-01");
There was no CNAME-chase/delegation step anywhere in the DCV path. This meant CERTInext's DCV only worked when the validation TXT record could be published in a zone the plugin's configured DNS provider directly controls for that exact domain. A customer who — as is common practice, to keep automation credentials out of the production zone — delegates their _dcv-challenge.<domain> (or similar) subdomain via CNAME to a separate, dedicated validation zone couldn't use CERTInext's DCV: the plugin would try to publish/verify against the production zone's DNS provider, which doesn't own the delegated record.
Severity
Medium — no crash, but blocked a common DCV deployment pattern outright.
How this was found
Found during a cross-repo DCV design comparison (2026-07-22) against sslstore-caplugin, cscglobal-caplugin, and acme-provider-caplugin's in-flight (unmerged) DNS-plugin branches. acme-provider-caplugin's dnsplugins branch (Keyfactor/acme-provider-caplugin#22) has a CnameResolver that follows CNAME hops up to a bounded depth with loop detection, and routes both the TXT record target and the DNS-provider-factory lookup through the resolved terminal name — the one clear "worth adopting" finding out of that comparison.
Fix
Fixed by #13 — added a bounded, loop-detected CNAME-resolve-then-route step ahead of the ResolveDomainValidator call in PerformDcvIfNeededAsync, using the resolved terminal name for both the TXT record target and the validator-factory lookup key. Gated behind a new DcvFollowCnameDelegation config flag (default off), so existing non-delegated deployments are unaffected.
Resolution is implemented via DnsClient.NET rather than a hand-rolled DNS client — deliberately avoided reinventing RFC 1035 wire-format parsing for a production CA plugin, and DnsClient.NET resolves correctly against OS-configured resolvers cross-platform (including reading /etc/resolv.conf in the containerized hosts this plugin runs on).
Covered by unit tests (hop-walking, loop detection, depth cap, no-op when the flag is off) and a live integration test that stages real CNAME records in Cloudflare and exercises the production resolver against them over the network.
Symptom
PerformDcvIfNeededAsync(CERTInextCAPlugin.cs:1442-1704) built the challenge hostname directly fromDcvTxtRecordTemplateagainst the raw enrollment domain and resolved the DNS provider against that same raw domain:There was no CNAME-chase/delegation step anywhere in the DCV path. This meant CERTInext's DCV only worked when the validation TXT record could be published in a zone the plugin's configured DNS provider directly controls for that exact domain. A customer who — as is common practice, to keep automation credentials out of the production zone — delegates their
_dcv-challenge.<domain>(or similar) subdomain via CNAME to a separate, dedicated validation zone couldn't use CERTInext's DCV: the plugin would try to publish/verify against the production zone's DNS provider, which doesn't own the delegated record.Severity
Medium — no crash, but blocked a common DCV deployment pattern outright.
How this was found
Found during a cross-repo DCV design comparison (2026-07-22) against
sslstore-caplugin,cscglobal-caplugin, andacme-provider-caplugin's in-flight (unmerged) DNS-plugin branches.acme-provider-caplugin'sdnspluginsbranch (Keyfactor/acme-provider-caplugin#22) has aCnameResolverthat follows CNAME hops up to a bounded depth with loop detection, and routes both the TXT record target and the DNS-provider-factory lookup through the resolved terminal name — the one clear "worth adopting" finding out of that comparison.Fix
Fixed by #13 — added a bounded, loop-detected CNAME-resolve-then-route step ahead of the
ResolveDomainValidatorcall inPerformDcvIfNeededAsync, using the resolved terminal name for both the TXT record target and the validator-factory lookup key. Gated behind a newDcvFollowCnameDelegationconfig flag (default off), so existing non-delegated deployments are unaffected.Resolution is implemented via
DnsClient.NETrather than a hand-rolled DNS client — deliberately avoided reinventing RFC 1035 wire-format parsing for a production CA plugin, andDnsClient.NETresolves correctly against OS-configured resolvers cross-platform (including reading/etc/resolv.confin the containerized hosts this plugin runs on).Covered by unit tests (hop-walking, loop detection, depth cap, no-op when the flag is off) and a live integration test that stages real CNAME records in Cloudflare and exercises the production resolver against them over the network.