Add EXEC_MODE=RAW support to DNS Provider exec (#586)

This commit is contained in:
Bill Shupp 2018-07-17 16:19:48 -07:00 committed by Ludovic Fernandez
parent 6edbd1572e
commit baad3dea99
3 changed files with 106 additions and 42 deletions

2
cli.go
View file

@ -230,7 +230,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tdnspod:\tDNSPOD_API_KEY") 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, "\totc:\tOTC_USER_NAME, OTC_PASSWORD, OTC_PROJECT_NAME, OTC_DOMAIN_NAME, OTC_IDENTITY_ENDPOINT")
fmt.Fprintln(w, "\tsakuracloud:\tSAKURACLOUD_ACCESS_TOKEN, SAKURACLOUD_ACCESS_TOKEN_SECRET") fmt.Fprintln(w, "\tsakuracloud:\tSAKURACLOUD_ACCESS_TOKEN, SAKURACLOUD_ACCESS_TOKEN_SECRET")
fmt.Fprintln(w, "\texec:\tEXEC_PATH") fmt.Fprintln(w, "\texec:\tEXEC_PATH, EXEC_MODE")
w.Flush() w.Flush()
fmt.Println(` fmt.Println(`

42
providers/dns/exec/doc.go Normal file
View file

@ -0,0 +1,42 @@
/*
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".
If you want to use the raw domain, token, and keyAuth values with your program, you can set `EXEC_MODE=RAW`:
EXEC_MODE=RAW \
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' like this:
./update-dns.sh "present" "foo.example.com." "--" "some-token" "KxAy-J3NwUmg9ZQuM-gP_Mq1nStaYSaP9tYQs5_-YsE.ksT-qywTd8058G-SHHWA3RAN72Pr0yWtPYmmY5UBpQ8"
NOTE:
The `--` is because the token MAY start with a `-`, and the called program may try and interpret a - as indicating a flag.
In the case of urfave, which is commonly used,
you can use the `--` delimiter to specify the start of positional arguments, and handle such a string safely.
*/
package exec

View file

@ -1,78 +1,100 @@
// 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 package exec
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
"github.com/xenolf/lego/log"
"github.com/xenolf/lego/platform/config/env"
) )
// Config Provider configuration.
type Config struct {
Program string
Mode string
}
// DNSProvider adds and removes the record for the DNS challenge by calling a // DNSProvider adds and removes the record for the DNS challenge by calling a
// program with command-line parameters. // program with command-line parameters.
type DNSProvider struct { type DNSProvider struct {
program string config *Config
} }
// NewDNSProvider returns a new DNS provider which runs the program in the // NewDNSProvider returns a new DNS provider which runs the program in the
// environment variable EXEC_PATH for adding and removing the DNS record. // environment variable EXEC_PATH for adding and removing the DNS record.
func NewDNSProvider() (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
s := os.Getenv("EXEC_PATH") values, err := env.Get("EXEC_PATH")
if s == "" { if err != nil {
return nil, errors.New("environment variable EXEC_PATH not set") return nil, fmt.Errorf("exec: %v", err)
} }
return NewDNSProviderProgram(s) return NewDNSProviderConfig(&Config{
Program: values["EXEC_PATH"],
Mode: os.Getenv("EXEC_MODE"),
})
}
// NewDNSProviderConfig returns a new DNS provider which runs the given configuration
// for adding and removing the DNS record.
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
if config == nil {
return nil, errors.New("the configuration is nil")
}
return &DNSProvider{config: config}, nil
} }
// NewDNSProviderProgram returns a new DNS provider which runs the given program // NewDNSProviderProgram returns a new DNS provider which runs the given program
// for adding and removing the DNS record. // for adding and removing the DNS record.
// Deprecated: use NewDNSProviderConfig instead
func NewDNSProviderProgram(program string) (*DNSProvider, error) { func NewDNSProviderProgram(program string) (*DNSProvider, error) {
return &DNSProvider{program: program}, nil if len(program) == 0 {
return nil, errors.New("the program is undefined")
}
return NewDNSProviderConfig(&Config{Program: program})
} }
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) var args []string
cmd := exec.Command(d.program, "present", fqdn, value, strconv.Itoa(ttl)) if d.config.Mode == "RAW" {
cmd.Stdout = os.Stdout args = []string{"present", "--", domain, token, keyAuth}
cmd.Stderr = os.Stderr } else {
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
args = []string{"present", fqdn, value, strconv.Itoa(ttl)}
}
return cmd.Run() cmd := exec.Command(d.config.Program, args...)
output, err := cmd.CombinedOutput()
if len(output) > 0 {
log.Println(string(output))
}
return err
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) var args []string
cmd := exec.Command(d.program, "cleanup", fqdn, value, strconv.Itoa(ttl)) if d.config.Mode == "RAW" {
cmd.Stdout = os.Stdout args = []string{"cleanup", "--", domain, token, keyAuth}
cmd.Stderr = os.Stderr } else {
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
args = []string{"cleanup", fqdn, value, strconv.Itoa(ttl)}
}
return cmd.Run() cmd := exec.Command(d.config.Program, args...)
output, err := cmd.CombinedOutput()
if len(output) > 0 {
log.Println(string(output))
}
return err
} }