Skip to content

No CNAME-delegation support in DCV (validation zone must equal the domain) #15

Description

@spbsoluble

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions