feat: add ACME DNS-PERSIST-01 challenge support (IETF draft-ietf-acme-dns-persist)

Standing TXT record at _validation-persist.<domain> eliminates per-renewal
DNS updates. Auto-fallback to dns-01 if CA doesn't offer dns-persist-01.
ScriptDNSSolver extended with PresentPersist method. Configurable via
CERTCTL_ACME_CHALLENGE_TYPE=dns-persist-01 and
CERTCTL_ACME_DNS_PERSIST_ISSUER_DOMAIN env vars.

Also fixes IsExpired edge-case test in discovery_test.go that always failed
due to time.Now() drift between test setup and method invocation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shankar0123
2026-03-26 14:23:46 -04:00
parent 5c38bc3bfe
commit e19c240a79
12 changed files with 325 additions and 50 deletions
+7 -6
View File
@@ -98,13 +98,14 @@ func main() {
logger.Info("initialized Local CA issuer connector")
// Initialize ACME issuer connector (for Let's Encrypt, Sectigo, etc.)
// Supports HTTP-01 (default) and DNS-01 (for wildcards) challenge types.
// Supports HTTP-01 (default), DNS-01 (for wildcards), and DNS-PERSIST-01 (standing record) challenge types.
acmeConnector := acmeissuer.New(&acmeissuer.Config{
DirectoryURL: os.Getenv("CERTCTL_ACME_DIRECTORY_URL"),
Email: os.Getenv("CERTCTL_ACME_EMAIL"),
ChallengeType: os.Getenv("CERTCTL_ACME_CHALLENGE_TYPE"),
DNSPresentScript: os.Getenv("CERTCTL_ACME_DNS_PRESENT_SCRIPT"),
DNSCleanUpScript: os.Getenv("CERTCTL_ACME_DNS_CLEANUP_SCRIPT"),
DirectoryURL: os.Getenv("CERTCTL_ACME_DIRECTORY_URL"),
Email: os.Getenv("CERTCTL_ACME_EMAIL"),
ChallengeType: os.Getenv("CERTCTL_ACME_CHALLENGE_TYPE"),
DNSPresentScript: os.Getenv("CERTCTL_ACME_DNS_PRESENT_SCRIPT"),
DNSCleanUpScript: os.Getenv("CERTCTL_ACME_DNS_CLEANUP_SCRIPT"),
DNSPersistIssuerDomain: os.Getenv("CERTCTL_ACME_DNS_PERSIST_ISSUER_DOMAIN"),
}, logger)
logger.Info("initialized ACME issuer connector")