From 5d557fdc6d9ee2226c0cce65733ee686ad4bc8b1 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 2 Apr 2019 18:38:23 +0200 Subject: [PATCH] Adds renew hook (#845) * chore: update golangci-lint. * feat: support renew-hook. --- .travis.yml | 2 +- acme/api/internal/secure/jws.go | 4 ---- cmd/cmd_renew.go | 36 +++++++++++++++++++++++++++--- docs/content/usage/cli/examples.md | 8 +++++++ internal/dnsdocs/generator.go | 1 - providers/dns/gandiv5/client.go | 12 +++++----- 6 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c4458cf..cad4568d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ before_install: - go get -u github.com/letsencrypt/pebble/... # Install linters and misspell - - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.15.0 + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.16.0 - golangci-lint --version # Hugo - documentation diff --git a/acme/api/internal/secure/jws.go b/acme/api/internal/secure/jws.go index 72c5a401..213aeda0 100644 --- a/acme/api/internal/secure/jws.go +++ b/acme/api/internal/secure/jws.go @@ -6,7 +6,6 @@ import ( "crypto/elliptic" "crypto/rsa" "encoding/base64" - "errors" "fmt" "github.com/go-acme/lego/acme/api/internal/nonces" @@ -118,9 +117,6 @@ func (j *JWS) GetKeyAuthorization(token string) (string, error) { // Generate the Key Authorization for the challenge jwk := &jose.JSONWebKey{Key: publicKey} - if jwk == nil { - return "", errors.New("could not generate JWK from key") - } thumbBytes, err := jwk.Thumbprint(crypto.SHA256) if err != nil { diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index f9f99b2c..f6fa666d 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -1,8 +1,13 @@ package cmd import ( + "context" "crypto" "crypto/x509" + "errors" + "fmt" + "os/exec" + "strings" "time" "github.com/go-acme/lego/certcrypto" @@ -47,6 +52,10 @@ func createRenew() cli.Command { Name: "must-staple", Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.", }, + cli.StringFlag{ + Name: "renew-hook", + Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.", + }, }, } } @@ -122,7 +131,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif certsStorage.SaveResource(certRes) - return nil + return renewHook(ctx) } func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool) error { @@ -158,8 +167,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat certsStorage.SaveResource(certRes) - return nil - + return renewHook(ctx) } func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool { @@ -194,3 +202,25 @@ func merge(prevDomains []string, nextDomains []string) []string { } return prevDomains } + +func renewHook(ctx *cli.Context) error { + hook := ctx.String("renew-hook") + if hook == "" { + return nil + } + + ctxCmd, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + parts := strings.Fields(hook) + output, err := exec.CommandContext(ctxCmd, parts[0], parts[1:]...).CombinedOutput() + if len(output) > 0 { + fmt.Println(string(output)) + } + + if ctxCmd.Err() == context.DeadlineExceeded { + return errors.New("hook timed out") + } + + return err +} diff --git a/docs/content/usage/cli/examples.md b/docs/content/usage/cli/examples.md index ed39834d..46e7065b 100644 --- a/docs/content/usage/cli/examples.md +++ b/docs/content/usage/cli/examples.md @@ -30,6 +30,14 @@ lego --email="foo@bar.com" --domains="example.com" --http renew lego --email="foo@bar.com" --domains="example.com" --http renew --days 45 ``` +### To renew the certificate (and hook) + +The hook is executed only when the certificates are effectively renewed. + +```bash +lego --email="foo@bar.com" --domains="example.com" --http renew --renew-hook="./myscript.sh" +``` + ### Obtain a certificate using the DNS challenge ```bash diff --git a/internal/dnsdocs/generator.go b/internal/dnsdocs/generator.go index a2d1742e..69b914f4 100644 --- a/internal/dnsdocs/generator.go +++ b/internal/dnsdocs/generator.go @@ -132,5 +132,4 @@ func generateCLIHelp(models *Providers) error { _, err = file.Write(source) return err - } diff --git a/providers/dns/gandiv5/client.go b/providers/dns/gandiv5/client.go index d583f1c1..5c2c2485 100644 --- a/providers/dns/gandiv5/client.go +++ b/providers/dns/gandiv5/client.go @@ -47,13 +47,13 @@ func (d *DNSProvider) addTXTRecord(domain string, name string, value string, ttl return err } - message := &apiResponse{} - err = d.do(req, message) + message := apiResponse{} + err = d.do(req, &message) if err != nil { return fmt.Errorf("unable to create TXT record for domain %s and name %s: %v", domain, name, err) } - if message != nil && len(message.Message) > 0 { + if len(message.Message) > 0 { log.Infof("API response: %s", message.Message) } @@ -87,13 +87,13 @@ func (d *DNSProvider) deleteTXTRecord(domain string, name string) error { return err } - message := &apiResponse{} - err = d.do(req, message) + message := apiResponse{} + err = d.do(req, &message) if err != nil { return fmt.Errorf("unable to delete TXT record for domain %s and name %s: %v", domain, name, err) } - if message != nil && len(message.Message) > 0 { + if len(message.Message) > 0 { log.Infof("API response: %s", message.Message) }