diff --git a/cli.go b/cli.go index 95801173..aed50127 100644 --- a/cli.go +++ b/cli.go @@ -226,6 +226,7 @@ Here is an example bash command using the CloudFlare DNS provider: fmt.Fprintln(w, "\tpdns:\tPDNS_API_KEY, PDNS_API_URL") fmt.Fprintln(w, "\tdnspod:\tDNSPOD_API_KEY") fmt.Fprintln(w, "\totc:\tOTC_USER_NAME, OTC_PASSWORD, OTC_PROJECT_NAME, OTC_DOMAIN_NAME, OTC_IDENTITY_ENDPOINT") + fmt.Fprintln(w, "\texec:\tEXEC_PATH") w.Flush() fmt.Println(` diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index b53c249f..437aa499 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -14,6 +14,7 @@ import ( "github.com/xenolf/lego/providers/dns/dnsmadeeasy" "github.com/xenolf/lego/providers/dns/dnspod" "github.com/xenolf/lego/providers/dns/dyn" + "github.com/xenolf/lego/providers/dns/exec" "github.com/xenolf/lego/providers/dns/exoscale" "github.com/xenolf/lego/providers/dns/gandi" "github.com/xenolf/lego/providers/dns/gandiv5" @@ -91,6 +92,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) provider, err = ns1.NewDNSProvider() case "otc": provider, err = otc.NewDNSProvider() + case "exec": + provider, err = exec.NewDNSProvider() default: err = fmt.Errorf("Unrecognised DNS provider: %s", name) } diff --git a/providers/dns/exec/exec.go b/providers/dns/exec/exec.go new file mode 100644 index 00000000..ee140ae7 --- /dev/null +++ b/providers/dns/exec/exec.go @@ -0,0 +1,72 @@ +// Package exec implements a manual DNS provider which runs a program for +// adding/removing the DNS record. +// +// The file name of the external program is specified in the environment +// variable EXEC_PATH. When it is run by lego, three command-line parameters +// are passed to it: The action ("present" or "cleanup"), the fully-qualified domain +// name, the value for the record and the TTL. +// +// For example, requesting a certificate for the domain 'foo.example.com' can +// be achieved by calling lego as follows: +// +// EXEC_PATH=./update-dns.sh \ +// lego --dns exec \ +// --domains foo.example.com \ +// --email invalid@example.com run +// +// It will then call the program './update-dns.sh' with like this: +// +// ./update-dns.sh "present" "_acme-challenge.foo.example.com." "MsijOYZxqyjGnFGwhjrhfg-Xgbl5r68WPda0J9EgqqI" "120" +// +// The program then needs to make sure the record is inserted. When it returns +// an error via a non-zero exit code, lego aborts. +// +// When the record is to be removed again, the program is called with the first +// command-line parameter set to "cleanup" instead of "present". +package exec + +import ( + "errors" + "os" + "os/exec" + "strconv" + + "github.com/xenolf/lego/acme" +) + +// DNSProvider adds and removes the record for the DNS challenge by calling a +// program with command-line parameters. +type DNSProvider struct { + program string +} + +// NewDNSProvider returns a new DNS provider which runs the program in the +// environment variable EXEC_PATH for adding and removing the DNS record. +func NewDNSProvider() (*DNSProvider, error) { + s := os.Getenv("EXEC_PATH") + if s == "" { + return nil, errors.New("environment variable EXEC_PATH not set") + } + + return &DNSProvider{program: s}, nil +} + +// Present creates a TXT record to fulfil the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) + cmd := exec.Command(d.program, "present", fqdn, value, strconv.Itoa(ttl)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +} + +// CleanUp removes the TXT record matching the specified parameters +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) + cmd := exec.Command(d.program, "cleanup", fqdn, value, strconv.Itoa(ttl)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd.Run() +}