feat: add hook on the run command. (#1157)

This commit is contained in:
Ludovic Fernandez 2020-05-14 23:44:08 +02:00 committed by GitHub
parent 2f1b384411
commit e07bf641ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 48 deletions

View file

@ -1,14 +1,8 @@
package cmd package cmd
import ( import (
"context"
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"time" "time"
"github.com/go-acme/lego/v3/certcrypto" "github.com/go-acme/lego/v3/certcrypto"
@ -145,7 +139,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt") meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key") meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
return renewHook(ctx, meta) return launchHook(ctx.String("renew-hook"), meta)
} }
func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
@ -185,7 +179,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt") meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key") meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
return renewHook(ctx, meta) return launchHook(ctx.String("renew-hook"), meta)
} }
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool { func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
@ -220,40 +214,3 @@ func merge(prevDomains []string, nextDomains []string) []string {
} }
return prevDomains return prevDomains
} }
func renewHook(ctx *cli.Context, meta map[string]string) error {
hook := ctx.String("renew-hook")
if hook == "" {
return nil
}
ctxCmd, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
parts := strings.Fields(hook)
cmdCtx := exec.CommandContext(ctxCmd, parts[0], parts[1:]...)
cmdCtx.Env = append(os.Environ(), metaToEnv(meta)...)
output, err := cmdCtx.CombinedOutput()
if len(output) > 0 {
fmt.Println(string(output))
}
if ctxCmd.Err() == context.DeadlineExceeded {
return errors.New("hook timed out")
}
return err
}
func metaToEnv(meta map[string]string) []string {
var envs []string
for k, v := range meta {
envs = append(envs, k+"="+v)
}
return envs
}

View file

@ -39,6 +39,10 @@ func createRun() cli.Command {
Name: "must-staple", 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.", 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: "run-hook",
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
},
}, },
} }
} }
@ -86,7 +90,14 @@ func run(ctx *cli.Context) error {
certsStorage.SaveResource(cert) certsStorage.SaveResource(cert)
return nil meta := map[string]string{
renewEnvAccountEmail: account.Email,
renewEnvCertDomain: cert.Domain,
renewEnvCertPath: certsStorage.GetFileName(cert.Domain, ".crt"),
renewEnvCertKeyPath: certsStorage.GetFileName(cert.Domain, ".key"),
}
return launchHook(ctx.String("run-hook"), meta)
} }
func handleTOS(ctx *cli.Context, client *lego.Client) bool { func handleTOS(ctx *cli.Context, client *lego.Client) bool {

47
cmd/hook.go Normal file
View file

@ -0,0 +1,47 @@
package cmd
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"time"
)
func launchHook(hook string, meta map[string]string) error {
if hook == "" {
return nil
}
ctxCmd, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
parts := strings.Fields(hook)
cmdCtx := exec.CommandContext(ctxCmd, parts[0], parts[1:]...)
cmdCtx.Env = append(os.Environ(), metaToEnv(meta)...)
output, err := cmdCtx.CombinedOutput()
if len(output) > 0 {
fmt.Println(string(output))
}
if ctxCmd.Err() == context.DeadlineExceeded {
return errors.New("hook timed out")
}
return err
}
func metaToEnv(meta map[string]string) []string {
var envs []string
for k, v := range meta {
envs = append(envs, k+"="+v)
}
return envs
}

View file

@ -37,11 +37,11 @@ GLOBAL OPTIONS:
--hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. --hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.
--key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "ec384") --key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "ec384")
--filename value (deprecated) Filename of the generated certificate. --filename value (deprecated) Filename of the generated certificate.
--path value Directory to use for storing the data. (default: "./.lego") --path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH]
--http Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges. --http Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges.
--http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80") --http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80")
--http.proxy-header value Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. (default: "Host") --http.proxy-header value Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy. (default: "Host")
--http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be served at /.well-known/acme-challenge --http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge
--http.memcached-host value Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts. --http.memcached-host value Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.
--tls Use the TLS challenge to solve challenges. Can be mixed with other types of challenges. --tls Use the TLS challenge to solve challenges. Can be mixed with other types of challenges.
--tls.port value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port. (default: ":443") --tls.port value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port. (default: ":443")

View file

@ -18,6 +18,21 @@ lego --email="foo@bar.com" --domains="example.com" --http run
(Find your certificate in the `.lego` folder of current working directory.) (Find your certificate in the `.lego` folder of current working directory.)
### Obtain a certificate (and hook)
The hook is executed only when the certificates are effectively created.
```bash
lego --email="foo@bar.com" --domains="example.com" --http run --run-hook="./myscript.sh"
```
Some information are added to the environment variables when the hook is used:
- `LEGO_ACCOUNT_EMAIL`: the email of the account.
- `LEGO_CERT_DOMAIN`: the main domain of the certificate.
- `LEGO_CERT_PATH`: the path of the certificate.
- `LEGO_CERT_KEY_PATH`: the path of the certificate key.
### To renew the certificate ### To renew the certificate
```bash ```bash