forked from TrueCloudLab/lego
feat: add hook on the run command. (#1157)
This commit is contained in:
parent
2f1b384411
commit
e07bf641ab
5 changed files with 78 additions and 48 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
47
cmd/hook.go
Normal 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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue