Skip to content

No in-Command guidance when DCV is enabled but no DNS provider is wired #16

Description

@spbsoluble

Symptom

When DcvEnabled=true but no IDomainValidatorFactory has been injected (e.g. the gateway host doesn't supply one, or no DNS provider plugin is installed), CERTInext already logs a clear startup warning (CERTInextCAPlugin.cs:261-272, tagged SOC2 CC7.1) and the per-order check at CERTInextCAPlugin.cs:1374 causes DCV to be silently skipped for that order. The order is then left parked at EXTERNALVALIDATION (per-domain dcvStatus="0") indefinitely — the sync-retry path (TryRunDcvDuringSyncAsync) keeps re-checking it every pass but can never complete it without a factory.

The operator has no way to see this from Keyfactor Command. The only signal is the plugin's own log file (which most Command operators enrolling certificates never look at). Nothing in the enrollment result surfaced "here is the TXT record you need to publish manually" — the order just sat pending with no actionable guidance in the UI.

Severity

Low/Medium — operator-experience gap, not a functional defect.

How this was found

Found during a cross-repo DCV design comparison (2026-07-22) against cscglobal-caplugin's in-flight (unmerged) DNS-plugin branch (Keyfactor/cscglobal-caplugin#12). That branch handles this via EnrollResult.EnrollmentContext — a Dictionary<string,string> of record-name → target value that gets returned to Command even when auto-publish succeeds or fails, so an operator without a DNS provider factory wired up still gets the record instructions surfaced directly in Command's enrollment result, rather than having to go dig through plugin logs.

Fix

Fixed by #13, scoped to the "no factory wired" case specifically (the common one — new gateway host, DNS provider plugin not yet installed), rather than every DCV failure mode. When PerformDcvIfNeededAsync can't proceed because no factory is wired, the plugin now best-effort looks up the pending DCV challenge(s) for the order and populates the enrollment result's EnrollmentContext/StatusMessage with the exact TXT record name and expected value, mirroring the EnrollmentContext pattern above. Falls back cleanly to the original pending-only message if the lookup itself fails.

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