From 17d938fc54b347d54a4f56c1f0d6e92c65033548 Mon Sep 17 00:00:00 2001
From: Matthew Holt <mholt@users.noreply.github.com>
Date: Sat, 8 Feb 2020 16:52:54 -0700
Subject: [PATCH] httpcaddyfile: Add support for DNS challenge solvers

Configuration via the Caddyfile requires use of env variables, but
an upstream issue is currently blocking that:
https://github.com/go-acme/lego/issues/1054

Providers will need to be retrofitted upstream in order to support env
var configuration.
---
 caddyconfig/httpcaddyfile/builtins.go | 16 +++++++++++++++
 caddyconfig/httpcaddyfile/httptype.go | 28 +++++++++++++++++++--------
 caddyconfig/httpcaddyfile/options.go  |  2 +-
 3 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 7c56b047b..29ca0be3c 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -98,6 +98,7 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
 //         alpn      <values...>
 //         load      <paths...>
 //         ca        <acme_ca_endpoint>
+//         dns       <provider_name>
 //     }
 //
 func parseTLS(h Helper) ([]ConfigValue, error) {
@@ -217,6 +218,21 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
 				}
 				mgr.CA = arg[0]
 
+			// DNS provider for ACME DNS challenge
+			case "dns":
+				if !h.Next() {
+					return nil, h.ArgErr()
+				}
+				provName := h.Val()
+				if mgr.Challenges == nil {
+					mgr.Challenges = new(caddytls.ChallengesConfig)
+				}
+				dnsProvModule, err := caddy.GetModule("tls.dns." + provName)
+				if err != nil {
+					return nil, h.Errf("getting DNS provider module named '%s': %v", provName, err)
+				}
+				mgr.Challenges.DNSRaw = caddyconfig.JSONModuleObject(dnsProvModule.New(), "provider", provName, h.warnings)
+
 			default:
 				return nil, h.Errf("unknown subdirective: %s", h.Val())
 			}
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 0cd2fca6e..268f66ae9 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -71,8 +71,8 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
 				val, err = parseOptExperimentalHTTP3(disp)
 			case "storage":
 				val, err = parseOptStorage(disp)
-			case "acme_ca":
-				val, err = parseOptACMECA(disp)
+			case "acme_ca", "acme_dns":
+				val, err = parseOptACME(disp)
 			case "email":
 				val, err = parseOptEmail(disp)
 			case "admin":
@@ -222,11 +222,12 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
 			}
 		}
 	}
-	// if global ACME CA or email were set, append a catch-all automation
+	// if global ACME CA, DNS, or email were set, append a catch-all automation
 	// policy that ensures they will be used if no tls directive was used
 	acmeCA, hasACMECA := options["acme_ca"]
+	acmeDNS, hasACMEDNS := options["acme_dns"]
 	email, hasEmail := options["email"]
-	if hasACMECA || hasEmail {
+	if hasACMECA || hasACMEDNS || hasEmail {
 		if tlsApp.Automation == nil {
 			tlsApp.Automation = new(caddytls.AutomationConfig)
 		}
@@ -236,11 +237,22 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
 		if !hasEmail {
 			email = ""
 		}
+		mgr := caddytls.ACMEManagerMaker{
+			CA:    acmeCA.(string),
+			Email: email.(string),
+		}
+		if hasACMEDNS {
+			provName := acmeDNS.(string)
+			dnsProvModule, err := caddy.GetModule("tls.dns." + provName)
+			if err != nil {
+				return nil, warnings, fmt.Errorf("getting DNS provider module named '%s': %v", provName, err)
+			}
+			mgr.Challenges = &caddytls.ChallengesConfig{
+				DNSRaw: caddyconfig.JSONModuleObject(dnsProvModule.New(), "provider", provName, &warnings),
+			}
+		}
 		tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
-			ManagementRaw: caddyconfig.JSONModuleObject(caddytls.ACMEManagerMaker{
-				CA:    acmeCA.(string),
-				Email: email.(string),
-			}, "module", "acme", &warnings),
+			ManagementRaw: caddyconfig.JSONModuleObject(mgr, "module", "acme", &warnings),
 		})
 	}
 	if tlsApp.Automation != nil {
diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go
index e81528e10..9ebefea84 100644
--- a/caddyconfig/httpcaddyfile/options.go
+++ b/caddyconfig/httpcaddyfile/options.go
@@ -162,7 +162,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
 	return storage, nil
 }
 
-func parseOptACMECA(d *caddyfile.Dispenser) (string, error) {
+func parseOptACME(d *caddyfile.Dispenser) (string, error) {
 	d.Next() // consume parameter name
 	if !d.Next() {
 		return "", d.ArgErr()