diff --git a/cli.go b/cli.go index 1329d252..ab6ad55f 100644 --- a/cli.go +++ b/cli.go @@ -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, "\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, "\texec:\tEXEC_PATH") + fmt.Fprintln(w, "\texec:\tEXEC_PATH, EXEC_MODE") w.Flush() fmt.Println(` diff --git a/providers/dns/exec/doc.go b/providers/dns/exec/doc.go new file mode 100644 index 00000000..9aa53fcc --- /dev/null +++ b/providers/dns/exec/doc.go @@ -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 diff --git a/providers/dns/exec/exec.go b/providers/dns/exec/exec.go index 9bd97d03..ea3a4398 100644 --- a/providers/dns/exec/exec.go +++ b/providers/dns/exec/exec.go @@ -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 import ( "errors" + "fmt" "os" "os/exec" "strconv" "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 // program with command-line parameters. type DNSProvider struct { - program string + config *Config } // 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") + values, err := env.Get("EXEC_PATH") + if err != nil { + 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 // for adding and removing the DNS record. +// Deprecated: use NewDNSProviderConfig instead 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. 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 + var args []string + if d.config.Mode == "RAW" { + args = []string{"present", "--", domain, token, keyAuth} + } 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 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 + var args []string + if d.config.Mode == "RAW" { + args = []string{"cleanup", "--", domain, token, keyAuth} + } 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 }