refactor: isolate flag usages (#2824)

This commit is contained in:
Ludovic Fernandez 2026-01-28 20:00:57 +01:00 committed by GitHub
commit d2f3cc457b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 558 additions and 567 deletions

View file

@ -11,14 +11,10 @@ import (
"github.com/go-acme/lego/v5/certcrypto"
"github.com/go-acme/lego/v5/cmd/internal/storage"
"github.com/go-acme/lego/v5/log"
"github.com/urfave/cli/v3"
)
const (
flgAccounts = "accounts"
flgNames = "names"
)
func createList() *cli.Command {
return &cli.Command{
Name: "list",
@ -28,22 +24,6 @@ func createList() *cli.Command {
}
}
func createListFlags() []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: flgAccounts,
Aliases: []string{"a"},
Usage: "Display accounts.",
},
&cli.BoolFlag{
Name: flgNames,
Aliases: []string{"n"},
Usage: "Display certificate names only.",
},
CreatePathFlag(false),
}
}
func list(ctx context.Context, cmd *cli.Command) error {
if cmd.Bool(flgAccounts) && !cmd.Bool(flgNames) {
if err := listAccount(ctx, cmd); err != nil {
@ -111,7 +91,10 @@ func listCertificates(_ context.Context, cmd *cli.Command) error {
}
func listAccount(_ context.Context, cmd *cli.Command) error {
accountsStorage := newAccountsStorage(cmd)
accountsStorage, err := storage.NewAccountsStorage(newAccountsStorageConfig(cmd))
if err != nil {
log.Fatal("Accounts storage initialization", log.ErrorAttr(err))
}
matches, err := filepath.Glob(filepath.Join(accountsStorage.GetRootPath(), "*", "*", "*.json"))
if err != nil {

82
cmd/cmd_register.go Normal file
View file

@ -0,0 +1,82 @@
package cmd
import (
"bufio"
"context"
"fmt"
"log/slog"
"os"
"strings"
"github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/log"
"github.com/go-acme/lego/v5/registration"
"github.com/urfave/cli/v3"
)
// TODO(ldez): add register command.
const rootPathWarningMessage = `!!!! HEADS UP !!!!
Your account credentials have been saved in your
configuration directory at "%s".
You should make a secure backup of this folder now. This
configuration directory will also contain private keys
generated by lego and certificates obtained from the ACME
server. Making regular backups of this folder is ideal.
`
func registerAccount(ctx context.Context, cmd *cli.Command, client *lego.Client) (*registration.Resource, error) {
accepted := handleTOS(cmd, client)
if !accepted {
log.Fatal("You did not accept the TOS. Unable to proceed.")
}
if cmd.Bool(flgEAB) {
kid := cmd.String(flgKID)
hmacEncoded := cmd.String(flgHMAC)
if kid == "" || hmacEncoded == "" {
log.Fatal(fmt.Sprintf("Requires arguments --%s and --%s.", flgKID, flgHMAC))
}
return client.Registration.RegisterWithExternalAccountBinding(ctx, registration.RegisterEABOptions{
TermsOfServiceAgreed: accepted,
Kid: kid,
HmacEncoded: hmacEncoded,
})
}
return client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
}
func handleTOS(cmd *cli.Command, client *lego.Client) bool {
// Check for a global accept override
if cmd.Bool(flgAcceptTOS) {
return true
}
reader := bufio.NewReader(os.Stdin)
log.Warn("Please review the TOS", slog.String("url", client.GetToSURL()))
for {
fmt.Println("Do you accept the TOS? Y/n")
text, err := reader.ReadString('\n')
if err != nil {
log.Fatal("Could not read from the console", log.ErrorAttr(err))
}
text = strings.Trim(text, "\r\n")
switch text {
case "", "y", "Y":
return true
case "n", "N":
return false
default:
fmt.Println("Your input was invalid. Please answer with one of Y/y, n/N or by pressing enter.")
}
}
}

View file

@ -25,19 +25,6 @@ import (
"github.com/urfave/cli/v3"
)
// Flag names.
const (
flgRenewDays = "days"
flgRenewDynamic = "dynamic"
flgARIDisable = "ari-disable"
flgARIWaitToRenewDuration = "ari-wait-to-renew-duration"
flgReuseKey = "reuse-key"
flgRenewHook = "renew-hook"
flgRenewHookTimeout = "renew-hook-timeout"
flgNoRandomSleep = "no-random-sleep"
flgForceCertDomains = "force-cert-domains"
)
func createRenew() *cli.Command {
return &cli.Command{
Name: "renew",
@ -66,102 +53,24 @@ func createRenew() *cli.Command {
}
}
func createRenewFlags() []cli.Flag {
flags := CreateFlags()
flags = append(flags,
&cli.IntFlag{
Name: flgRenewDays,
Value: 30,
Usage: "The number of days left on a certificate to renew it.",
},
// TODO(ldez): in v5, remove this flag, use this behavior as default.
&cli.BoolFlag{
Name: flgRenewDynamic,
Value: false,
Usage: "Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5.",
},
&cli.BoolFlag{
Name: flgARIDisable,
Usage: "Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed.",
},
&cli.DurationFlag{
Name: flgARIWaitToRenewDuration,
Usage: "The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint.",
},
&cli.BoolFlag{
Name: flgReuseKey,
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
},
&cli.BoolFlag{
Name: flgNoBundle,
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
&cli.BoolFlag{
Name: flgMustStaple,
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
" Only works if the CSR is generated by lego.",
},
&cli.TimestampFlag{
Name: flgNotBefore,
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.TimestampFlag{
Name: flgNotAfter,
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.StringFlag{
Name: flgPreferredChain,
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." +
" If no match, the default offered chain will be used.",
},
&cli.StringFlag{
Name: flgProfile,
Usage: "If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.",
},
&cli.StringFlag{
Name: flgAlwaysDeactivateAuthorizations,
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
},
&cli.StringFlag{
Name: flgRenewHook,
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
},
&cli.DurationFlag{
Name: flgRenewHookTimeout,
Usage: "Define the timeout for the hook execution.",
Value: 2 * time.Minute,
},
&cli.BoolFlag{
Name: flgNoRandomSleep,
Usage: "Do not add a random sleep before the renewal." +
" We do not recommend using this flag if you are doing your renewals in an automated way.",
},
&cli.BoolFlag{
Name: flgForceCertDomains,
Usage: "Check and ensure that the cert's domain list matches those passed in the domains argument.",
},
)
return flags
}
func renew(ctx context.Context, cmd *cli.Command) error {
account, keyType := setupAccount(ctx, cmd, newAccountsStorage(cmd))
accountsStorage, err := storage.NewAccountsStorage(newAccountsStorageConfig(cmd))
if err != nil {
log.Fatal("Accounts storage initialization", log.ErrorAttr(err))
}
keyType := getKeyType(cmd)
account := setupAccount(ctx, keyType, accountsStorage)
if account.Registration == nil {
log.Fatal("The account is not registered. Use 'run' to register a new account.", slog.String("email", account.Email))
}
certsStorage := newCertificatesStorage(cmd)
bundle := !cmd.Bool(flgNoBundle)
certsStorage, err := storage.NewCertificatesStorage(newCertificatesWriterConfig(cmd))
if err != nil {
log.Fatal("Certificates storage", log.ErrorAttr(err))
}
meta := map[string]string{
hook.EnvAccountEmail: account.Email,
@ -169,14 +78,14 @@ func renew(ctx context.Context, cmd *cli.Command) error {
// CSR
if cmd.IsSet(flgCSR) {
return renewForCSR(ctx, cmd, account, keyType, certsStorage, bundle, meta)
return renewForCSR(ctx, cmd, account, keyType, certsStorage, meta)
}
// Domains
return renewForDomains(ctx, cmd, account, keyType, certsStorage, bundle, meta)
return renewForDomains(ctx, cmd, account, keyType, certsStorage, meta)
}
func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Account, keyType certcrypto.KeyType, certsStorage *storage.CertificatesStorage, meta map[string]string) error {
domains := cmd.StringSlice(flgDomains)
domain := domains[0]
@ -185,7 +94,10 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc
// as web servers would not be able to work with a combined file.
certificates, err := certsStorage.ReadCertificate(domain, storage.ExtCert)
if err != nil {
log.Fatal("Error while loading the certificate.", log.DomainAttr(domain), log.ErrorAttr(err))
log.Fatal("Error while loading the certificate.",
log.DomainAttr(domain),
log.ErrorAttr(err),
)
}
cert := certificates[0]
@ -200,7 +112,9 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc
if !cmd.Bool(flgARIDisable) {
client = setupClient(cmd, account, keyType)
ariRenewalTime = getARIRenewalTime(ctx, cmd, cert, domain, client)
willingToSleep := cmd.Duration(flgARIWaitToRenewDuration)
ariRenewalTime = getARIRenewalTime(ctx, willingToSleep, cert, domain, client)
if ariRenewalTime != nil {
now := time.Now().UTC()
@ -276,17 +190,9 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc
renewalDomains = merge(certDomains, domains)
}
request := certificate.ObtainRequest{
Domains: renewalDomains,
PrivateKey: privateKey,
MustStaple: cmd.Bool(flgMustStaple),
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
request := newObtainRequest(cmd, renewalDomains)
request.PrivateKey = privateKey
if replacesCertID != "" {
request.ReplacesCertID = replacesCertID
@ -301,17 +207,16 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc
certsStorage.SaveResource(certRes)
addPathToMetadata(meta, domain, certRes, certsStorage)
hook.AddPathToMetadata(meta, certRes.Domain, certRes, certsStorage)
return hook.Launch(ctx, cmd.String(flgRenewHook), cmd.Duration(flgRenewHookTimeout), meta)
}
func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account, keyType certcrypto.KeyType, certsStorage *storage.CertificatesStorage, meta map[string]string) error {
csr, err := readCSRFile(cmd.String(flgCSR))
if err != nil {
log.Fatal("Could not read CSR file.",
slog.String("flag", flgCSR),
slog.String("filepath", cmd.String(flgCSR)),
slog.String(flgCSR, cmd.String(flgCSR)),
log.ErrorAttr(err),
)
}
@ -344,7 +249,9 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account
if !cmd.Bool(flgARIDisable) {
client = setupClient(cmd, account, keyType)
ariRenewalTime = getARIRenewalTime(ctx, cmd, cert, domain, client)
willingToSleep := cmd.Duration(flgARIWaitToRenewDuration)
ariRenewalTime = getARIRenewalTime(ctx, willingToSleep, cert, domain, client)
if ariRenewalTime != nil {
now := time.Now().UTC()
@ -353,7 +260,8 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account
log.Info("Sleeping until renewal time",
log.DomainAttr(domain),
slog.Duration("sleep", ariRenewalTime.Sub(now)),
slog.Time("renewalTime", *ariRenewalTime))
slog.Time("renewalTime", *ariRenewalTime),
)
time.Sleep(ariRenewalTime.Sub(now))
}
}
@ -379,15 +287,7 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account
slog.Int("hoursRemaining", int(timeLeft.Hours())),
)
request := certificate.ObtainForCSRRequest{
CSR: csr,
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
request := newObtainForCSRRequest(cmd, csr)
if replacesCertID != "" {
request.ReplacesCertID = replacesCertID
@ -400,7 +300,7 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account
certsStorage.SaveResource(certRes)
addPathToMetadata(meta, domain, certRes, certsStorage)
hook.AddPathToMetadata(meta, domain, certRes, certsStorage)
return hook.Launch(ctx, cmd.String(flgRenewHook), cmd.Duration(flgRenewHookTimeout), meta)
}
@ -453,7 +353,7 @@ func needRenewalDynamic(x509Cert *x509.Certificate, domain string, now time.Time
}
// getARIRenewalTime checks if the certificate needs to be renewed using the renewalInfo endpoint.
func getARIRenewalTime(ctx context.Context, cmd *cli.Command, cert *x509.Certificate, domain string, client *lego.Client) *time.Time {
func getARIRenewalTime(ctx context.Context, willingToSleep time.Duration, cert *x509.Certificate, domain string, client *lego.Client) *time.Time {
if cert.IsCA {
log.Fatal("Certificate bundle starts with a CA certificate.", log.DomainAttr(domain))
}
@ -479,7 +379,7 @@ func getARIRenewalTime(ctx context.Context, cmd *cli.Command, cert *x509.Certifi
now := time.Now().UTC()
renewalTime := renewalInfo.ShouldRenewAt(now, cmd.Duration(flgARIWaitToRenewDuration))
renewalTime := renewalInfo.ShouldRenewAt(now, willingToSleep)
if renewalTime == nil {
log.Info("acme: renewalInfo endpoint indicates that renewal is not needed.", log.DomainAttr(domain))
return nil

View file

@ -4,18 +4,11 @@ import (
"context"
"log/slog"
"github.com/go-acme/lego/v5/acme"
"github.com/go-acme/lego/v5/cmd/internal/storage"
"github.com/go-acme/lego/v5/log"
"github.com/urfave/cli/v3"
)
// Flag names.
const (
flgKeep = "keep"
flgReason = "reason"
)
func createRevoke() *cli.Command {
return &cli.Command{
Name: "revoke",
@ -25,32 +18,15 @@ func createRevoke() *cli.Command {
}
}
func createRevokeFlags() []cli.Flag {
flags := CreateFlags()
flags = append(flags,
&cli.BoolFlag{
Name: flgKeep,
Aliases: []string{"k"},
Usage: "Keep the certificates after the revocation instead of archiving them.",
},
&cli.UintFlag{
Name: flgReason,
Usage: "Identifies the reason for the certificate revocation." +
" See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." +
" Valid values are:" +
" 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged)," +
" 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL)," +
" 9 (privilegeWithdrawn), or 10 (aACompromise).",
Value: acme.CRLReasonUnspecified,
},
)
return flags
}
func revoke(ctx context.Context, cmd *cli.Command) error {
account, keyType := setupAccount(ctx, cmd, newAccountsStorage(cmd))
accountsStorage, err := storage.NewAccountsStorage(newAccountsStorageConfig(cmd))
if err != nil {
log.Fatal("Accounts storage initialization", log.ErrorAttr(err))
}
keyType := getKeyType(cmd)
account := setupAccount(ctx, keyType, accountsStorage)
if account.Registration == nil {
log.Fatal("Account is not registered. Use 'run' to register a new account.", slog.String("email", account.Email))
@ -58,7 +34,11 @@ func revoke(ctx context.Context, cmd *cli.Command) error {
client := newClient(cmd, account, keyType)
certsStorage := newCertificatesStorage(cmd)
certsStorage, err := storage.NewCertificatesStorage(newCertificatesWriterConfig(cmd))
if err != nil {
log.Fatal("Certificates storage", log.ErrorAttr(err))
}
certsStorage.CreateRootFolder()
for _, domain := range cmd.StringSlice(flgDomains) {

View file

@ -1,13 +1,9 @@
package cmd
import (
"bufio"
"context"
"crypto/x509"
"fmt"
"log/slog"
"os"
"strings"
"time"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/cmd/internal/hook"
@ -18,20 +14,6 @@ import (
"github.com/urfave/cli/v3"
)
// Flag names.
const (
flgNoBundle = "no-bundle"
flgMustStaple = "must-staple"
flgNotBefore = "not-before"
flgNotAfter = "not-after"
flgPrivateKey = "private-key"
flgPreferredChain = "preferred-chain"
flgProfile = "profile"
flgAlwaysDeactivateAuthorizations = "always-deactivate-authorizations"
flgRunHook = "run-hook"
flgRunHookTimeout = "run-hook-timeout"
)
func createRun() *cli.Command {
return &cli.Command{
Name: "run",
@ -56,84 +38,22 @@ func createRun() *cli.Command {
}
}
func createRunFlags() []cli.Flag {
flags := CreateFlags()
flags = append(flags,
&cli.BoolFlag{
Name: flgNoBundle,
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
&cli.BoolFlag{
Name: flgMustStaple,
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
" Only works if the CSR is generated by lego.",
},
&cli.TimestampFlag{
Name: flgNotBefore,
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.TimestampFlag{
Name: flgNotAfter,
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.StringFlag{
Name: flgPrivateKey,
Usage: "Path to private key (in PEM encoding) for the certificate. By default, the private key is generated.",
},
&cli.StringFlag{
Name: flgPreferredChain,
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." +
" If no match, the default offered chain will be used.",
},
&cli.StringFlag{
Name: flgProfile,
Usage: "If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.",
},
&cli.StringFlag{
Name: flgAlwaysDeactivateAuthorizations,
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
},
&cli.StringFlag{
Name: flgRunHook,
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
},
&cli.DurationFlag{
Name: flgRunHookTimeout,
Usage: "Define the timeout for the hook execution.",
Value: 2 * time.Minute,
},
)
return flags
}
const rootPathWarningMessage = `!!!! HEADS UP !!!!
Your account credentials have been saved in your
configuration directory at "%s".
You should make a secure backup of this folder now. This
configuration directory will also contain private keys
generated by lego and certificates obtained from the ACME
server. Making regular backups of this folder is ideal.
`
func run(ctx context.Context, cmd *cli.Command) error {
accountsStorage := newAccountsStorage(cmd)
accountsStorage, err := storage.NewAccountsStorage(newAccountsStorageConfig(cmd))
if err != nil {
log.Fatal("Accounts storage initialization", log.ErrorAttr(err))
}
account, keyType := setupAccount(ctx, cmd, accountsStorage)
keyType := getKeyType(cmd)
account := setupAccount(ctx, keyType, accountsStorage)
client := setupClient(cmd, account, keyType)
if account.Registration == nil {
reg, err := register(ctx, cmd, client)
var reg *registration.Resource
reg, err = registerAccount(ctx, cmd, client)
if err != nil {
log.Fatal("Could not complete registration.", log.ErrorAttr(err))
}
@ -146,7 +66,11 @@ func run(ctx context.Context, cmd *cli.Command) error {
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
}
certsStorage := newCertificatesStorage(cmd)
certsStorage, err := storage.NewCertificatesStorage(newCertificatesWriterConfig(cmd))
if err != nil {
log.Fatal("Certificates storage", log.ErrorAttr(err))
}
certsStorage.CreateRootFolder()
cert, err := obtainCertificate(ctx, cmd, client)
@ -162,82 +86,19 @@ func run(ctx context.Context, cmd *cli.Command) error {
hook.EnvAccountEmail: account.Email,
}
addPathToMetadata(meta, cert.Domain, cert, certsStorage)
hook.AddPathToMetadata(meta, cert.Domain, cert, certsStorage)
return hook.Launch(ctx, cmd.String(flgRunHook), cmd.Duration(flgRunHookTimeout), meta)
}
func handleTOS(cmd *cli.Command, client *lego.Client) bool {
// Check for a global accept override
if cmd.Bool(flgAcceptTOS) {
return true
}
reader := bufio.NewReader(os.Stdin)
log.Warn("Please review the TOS", slog.String("url", client.GetToSURL()))
for {
fmt.Println("Do you accept the TOS? Y/n")
text, err := reader.ReadString('\n')
if err != nil {
log.Fatal("Could not read from the console", log.ErrorAttr(err))
}
text = strings.Trim(text, "\r\n")
switch text {
case "", "y", "Y":
return true
case "n", "N":
return false
default:
fmt.Println("Your input was invalid. Please answer with one of Y/y, n/N or by pressing enter.")
}
}
}
func register(ctx context.Context, cmd *cli.Command, client *lego.Client) (*registration.Resource, error) {
accepted := handleTOS(cmd, client)
if !accepted {
log.Fatal("You did not accept the TOS. Unable to proceed.")
}
if cmd.Bool(flgEAB) {
kid := cmd.String(flgKID)
hmacEncoded := cmd.String(flgHMAC)
if kid == "" || hmacEncoded == "" {
log.Fatal(fmt.Sprintf("Requires arguments --%s and --%s.", flgKID, flgHMAC))
}
return client.Registration.RegisterWithExternalAccountBinding(ctx, registration.RegisterEABOptions{
TermsOfServiceAgreed: accepted,
Kid: kid,
HmacEncoded: hmacEncoded,
})
}
return client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
}
func obtainCertificate(ctx context.Context, cmd *cli.Command, client *lego.Client) (*certificate.Resource, error) {
bundle := !cmd.Bool(flgNoBundle)
domains := cmd.StringSlice(flgDomains)
if len(domains) > 0 {
// obtain a certificate, generating a new private key
request := certificate.ObtainRequest{
Domains: domains,
MustStaple: cmd.Bool(flgMustStaple),
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
request := newObtainRequest(cmd, domains)
// TODO(ldez): factorize?
if cmd.IsSet(flgPrivateKey) {
var err error
@ -257,16 +118,9 @@ func obtainCertificate(ctx context.Context, cmd *cli.Command, client *lego.Clien
}
// obtain a certificate for this CSR
request := certificate.ObtainForCSRRequest{
CSR: csr,
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: bundle,
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
request := newObtainForCSRRequest(cmd, csr)
// TODO(ldez): factorize?
if cmd.IsSet(flgPrivateKey) {
var err error
@ -278,3 +132,28 @@ func obtainCertificate(ctx context.Context, cmd *cli.Command, client *lego.Clien
return client.Certificate.ObtainForCSR(ctx, request)
}
func newObtainRequest(cmd *cli.Command, domains []string) certificate.ObtainRequest {
return certificate.ObtainRequest{
Domains: domains,
MustStaple: cmd.Bool(flgMustStaple),
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: !cmd.Bool(flgNoBundle),
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
}
func newObtainForCSRRequest(cmd *cli.Command, csr *x509.CertificateRequest) certificate.ObtainForCSRRequest {
return certificate.ObtainForCSRRequest{
CSR: csr,
NotBefore: cmd.Timestamp(flgNotBefore),
NotAfter: cmd.Timestamp(flgNotAfter),
Bundle: !cmd.Bool(flgNoBundle),
PreferredChain: cmd.String(flgPreferredChain),
Profile: cmd.String(flgProfile),
AlwaysDeactivateAuthorizations: cmd.Bool(flgAlwaysDeactivateAuthorizations),
}
}

View file

@ -4,7 +4,9 @@ import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/go-acme/lego/v5/acme"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/cmd/internal/storage"
"github.com/go-acme/lego/v5/lego"
@ -17,15 +19,25 @@ const (
flgDomains = "domains"
flgAcceptTOS = "accept-tos"
flgEmail = "email"
flgCSR = "csr"
flgEAB = "eab"
flgKID = "kid"
flgHMAC = "hmac"
)
// Flag names related to Obtain certificates.
const (
flgCSR = "csr"
flgNoBundle = "no-bundle"
flgMustStaple = "must-staple"
flgNotBefore = "not-before"
flgNotAfter = "not-after"
flgPreferredChain = "preferred-chain"
flgProfile = "profile"
flgAlwaysDeactivateAuthorizations = "always-deactivate-authorizations"
)
// Flag names related to the output.
const (
flgFilename = "filename"
flgPath = "path"
flgPEM = "pem"
flgPFX = "pfx"
@ -74,6 +86,43 @@ const (
flgDNSTimeout = "dns-timeout"
)
// Flags names related to hooks.
const (
flgRenewHook = "renew-hook"
flgRenewHookTimeout = "renew-hook-timeout"
flgRunHook = "run-hook"
flgRunHookTimeout = "run-hook-timeout"
)
// Flag names related to the specific run command.
const (
flgPrivateKey = "private-key"
)
// Flag names related to the specific renew command.
const (
flgRenewDays = "days"
flgRenewDynamic = "dynamic"
flgARIDisable = "ari-disable"
flgARIWaitToRenewDuration = "ari-wait-to-renew-duration"
flgReuseKey = "reuse-key"
flgNoRandomSleep = "no-random-sleep"
flgForceCertDomains = "force-cert-domains"
)
// Flag names related to the specific revoke command.
const (
flgKeep = "keep"
flgReason = "reason"
)
// Flag names related to the list command.
const (
flgAccounts = "accounts"
flgNames = "names"
)
// Environment variable names.
const (
envEAB = "LEGO_EAB"
@ -132,6 +181,16 @@ func CreateACMEClientFlags() []cli.Flag {
}
}
func CreateChallengesFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, CreateHTTPChallengeFlags()...)
flags = append(flags, CreateTLSChallengeFlags()...)
flags = append(flags, CreateDNSChallengeFlags()...)
return flags
}
func CreateHTTPChallengeFlags() []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
@ -227,10 +286,6 @@ func CreateDNSChallengeFlags() []cli.Flag {
func CreateOutputFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: flgFilename,
Usage: "(deprecated) Filename of the generated certificate.",
},
CreatePathFlag(true),
&cli.BoolFlag{
Name: flgPEM,
@ -256,27 +311,6 @@ func CreateOutputFlags() []cli.Flag {
}
}
func CreatePathFlag(forceCreation bool) cli.Flag {
return &cli.StringFlag{
Name: flgPath,
Sources: cli.NewValueSourceChain(cli.EnvVar(envPath), &defaultPathValueSource{}),
Usage: "Directory to use for storing the data.",
Validator: func(s string) error {
if !forceCreation {
return nil
}
err := storage.CreateNonExistingFolder(s)
if err != nil {
return fmt.Errorf("could not check/create the path %q: %w", s, err)
}
return nil
},
Required: true,
}
}
func CreateAccountFlags() []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
@ -290,11 +324,6 @@ func CreateAccountFlags() []cli.Flag {
Sources: cli.EnvVars(envEmail),
Usage: "Email used for registration and recovery contact.",
},
&cli.StringFlag{
Name: flgCSR,
Aliases: []string{"c"},
Usage: "Certificate signing request filename, if an external CSR is to be used.",
},
&cli.BoolFlag{
Name: flgEAB,
Sources: cli.EnvVars(envEAB),
@ -313,25 +342,208 @@ func CreateAccountFlags() []cli.Flag {
}
}
func CreateFlags() []cli.Flag {
flags := []cli.Flag{
&cli.StringSliceFlag{
Name: flgDomains,
Aliases: []string{"d"},
Usage: "Add a domain to the process. Can be specified multiple times.",
func CreateObtainFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: flgCSR,
Aliases: []string{"c"},
Usage: "Certificate signing request filename, if an external CSR is to be used.",
},
&cli.BoolFlag{
Name: flgNoBundle,
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
},
&cli.BoolFlag{
Name: flgMustStaple,
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate." +
" Only works if the CSR is generated by lego.",
},
&cli.TimestampFlag{
Name: flgNotBefore,
Usage: "Set the notBefore field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.TimestampFlag{
Name: flgNotAfter,
Usage: "Set the notAfter field in the certificate (RFC3339 format)",
Config: cli.TimestampConfig{
Layouts: []string{time.RFC3339},
},
},
&cli.StringFlag{
Name: flgPreferredChain,
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." +
" If no match, the default offered chain will be used.",
},
&cli.StringFlag{
Name: flgProfile,
Usage: "If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.",
},
&cli.StringFlag{
Name: flgAlwaysDeactivateAuthorizations,
Usage: "Force the authorizations to be relinquished even if the certificate request was successful.",
},
}
}
func CreateHookFlags(name, timeoutName string) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: name,
Usage: "Define a hook. The hook is executed only when the certificates are effectively created/renewed.",
},
&cli.DurationFlag{
Name: timeoutName,
Usage: "Define the timeout for the hook execution.",
Value: 2 * time.Minute,
},
}
}
func CreateBaseFlags() []cli.Flag {
var flags []cli.Flag
flags = append(flags, CreateDomainFlag())
flags = append(flags, CreateAccountFlags()...)
flags = append(flags, CreateACMEClientFlags()...)
flags = append(flags, CreateOutputFlags()...)
flags = append(flags, CreateHTTPChallengeFlags()...)
flags = append(flags, CreateTLSChallengeFlags()...)
flags = append(flags, CreateDNSChallengeFlags()...)
return flags
}
func createRunFlags() []cli.Flag {
flags := CreateBaseFlags()
flags = append(flags, CreateChallengesFlags()...)
flags = append(flags, CreateObtainFlags()...)
flags = append(flags, CreateHookFlags(flgRunHook, flgRunHookTimeout)...)
flags = append(flags,
&cli.StringFlag{
Name: flgPrivateKey,
Usage: "Path to private key (in PEM encoding) for the certificate. By default, the private key is generated.",
},
)
return flags
}
func createRenewFlags() []cli.Flag {
flags := CreateBaseFlags()
flags = append(flags, CreateChallengesFlags()...)
flags = append(flags, CreateObtainFlags()...)
flags = append(flags, CreateHookFlags(flgRenewHook, flgRenewHookTimeout)...)
flags = append(flags,
&cli.IntFlag{
Name: flgRenewDays,
Value: 30,
Usage: "The number of days left on a certificate to renew it.",
},
// TODO(ldez): in v5, remove this flag, use this behavior as default.
&cli.BoolFlag{
Name: flgRenewDynamic,
Value: false,
Usage: "Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5.",
},
&cli.BoolFlag{
Name: flgARIDisable,
Usage: "Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed.",
},
&cli.DurationFlag{
Name: flgARIWaitToRenewDuration,
Usage: "The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint.",
},
&cli.BoolFlag{
Name: flgReuseKey,
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
},
&cli.BoolFlag{
Name: flgNoRandomSleep,
Usage: "Do not add a random sleep before the renewal." +
" We do not recommend using this flag if you are doing your renewals in an automated way.",
},
&cli.BoolFlag{
Name: flgForceCertDomains,
Usage: "Check and ensure that the cert's domain list matches those passed in the domains argument.",
},
)
return flags
}
func createRevokeFlags() []cli.Flag {
flags := CreateBaseFlags()
flags = append(flags,
&cli.BoolFlag{
Name: flgKeep,
Aliases: []string{"k"},
Usage: "Keep the certificates after the revocation instead of archiving them.",
},
&cli.UintFlag{
Name: flgReason,
Usage: "Identifies the reason for the certificate revocation." +
" See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1." +
" Valid values are:" +
" 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged)," +
" 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL)," +
" 9 (privilegeWithdrawn), or 10 (aACompromise).",
Value: acme.CRLReasonUnspecified,
},
)
return flags
}
func createListFlags() []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: flgAccounts,
Aliases: []string{"a"},
Usage: "Display accounts.",
},
&cli.BoolFlag{
Name: flgNames,
Aliases: []string{"n"},
Usage: "Display certificate names only.",
},
CreatePathFlag(false),
}
}
func CreateDomainFlag() cli.Flag {
return &cli.StringSliceFlag{
Name: flgDomains,
Aliases: []string{"d"},
Usage: "Add a domain to the process. Can be specified multiple times or use comma as a separator.",
}
}
func CreatePathFlag(forceCreation bool) cli.Flag {
return &cli.StringFlag{
Name: flgPath,
Sources: cli.NewValueSourceChain(cli.EnvVar(envPath), &defaultPathValueSource{}),
Usage: "Directory to use for storing the data.",
Validator: func(s string) error {
if !forceCreation {
return nil
}
err := storage.CreateNonExistingFolder(s)
if err != nil {
return fmt.Errorf("could not check/create the path %q: %w", s, err)
}
return nil
},
Required: true,
}
}
// defaultPathValueSource gets the default path based on the current working directory.
// The field value is only here because clihelp/generator.
type defaultPathValueSource struct{}

View file

@ -1,25 +0,0 @@
package cmd
import (
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/cmd/internal/hook"
"github.com/go-acme/lego/v5/cmd/internal/storage"
)
func addPathToMetadata(meta map[string]string, domain string, certRes *certificate.Resource, certsStorage *CertificatesStorage) {
meta[hook.EnvCertDomain] = domain
meta[hook.EnvCertPath] = certsStorage.GetFileName(domain, storage.ExtCert)
meta[hook.EnvCertKeyPath] = certsStorage.GetFileName(domain, storage.ExtKey)
if certRes.IssuerCertificate != nil {
meta[hook.EnvIssuerCertKeyPath] = certsStorage.GetFileName(domain, storage.ExtIssuer)
}
if certsStorage.IsPEM() {
meta[hook.EnvCertPEMPath] = certsStorage.GetFileName(domain, storage.ExtPEM)
}
if certsStorage.IsPFX() {
meta[hook.EnvCertPFXPath] = certsStorage.GetFileName(domain, storage.ExtPFX)
}
}

View file

@ -9,6 +9,9 @@ import (
"os/exec"
"strings"
"time"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/cmd/internal/storage"
)
const (
@ -82,3 +85,22 @@ func metaToEnv(meta map[string]string) []string {
return envs
}
// AddPathToMetadata adds information about the certificate to the metadata map.
func AddPathToMetadata(meta map[string]string, domain string, certRes *certificate.Resource, certsStorage *storage.CertificatesStorage) {
meta[EnvCertDomain] = domain
meta[EnvCertPath] = certsStorage.GetFileName(domain, storage.ExtCert)
meta[EnvCertKeyPath] = certsStorage.GetFileName(domain, storage.ExtKey)
if certRes.IssuerCertificate != nil {
meta[EnvIssuerCertKeyPath] = certsStorage.GetFileName(domain, storage.ExtIssuer)
}
if certsStorage.IsPEM() {
meta[EnvCertPEMPath] = certsStorage.GetFileName(domain, storage.ExtPEM)
}
if certsStorage.IsPFX() {
meta[EnvCertPFXPath] = certsStorage.GetFileName(domain, storage.ExtPFX)
}
}

View file

@ -24,7 +24,7 @@ func (a *Account) GetEmail() string {
return a.Email
}
// GetPrivateKey returns the private RSA account key.
// GetPrivateKey returns the private account key.
func (a *Account) GetPrivateKey() crypto.PrivateKey {
return a.key
}

View file

@ -1,8 +1,13 @@
package storage
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/go-acme/lego/v5/log"
"golang.org/x/net/idna"
)
const (
@ -19,12 +24,23 @@ const (
baseArchivesFolderName = "archives"
)
func getCertificatesRootPath(basePath string) string {
return filepath.Join(basePath, baseCertificatesFolderName)
// CertificatesStorage a certificates' storage.
type CertificatesStorage struct {
*CertificatesWriter
*CertificatesReader
}
func getCertificatesArchivePath(basePath string) string {
return filepath.Join(basePath, baseArchivesFolderName)
// NewCertificatesStorage create a new certificates storage.
func NewCertificatesStorage(config CertificatesWriterConfig) (*CertificatesStorage, error) {
writer, err := NewCertificatesWriter(config)
if err != nil {
return nil, fmt.Errorf("certificates storage writer: %w", err)
}
return &CertificatesStorage{
CertificatesWriter: writer,
CertificatesReader: NewCertificatesReader(config.BasePath),
}, nil
}
func CreateNonExistingFolder(path string) error {
@ -36,3 +52,24 @@ func CreateNonExistingFolder(path string) error {
return nil
}
func getCertificatesRootPath(basePath string) string {
return filepath.Join(basePath, baseCertificatesFolderName)
}
func getCertificatesArchivePath(basePath string) string {
return filepath.Join(basePath, baseArchivesFolderName)
}
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
func sanitizedDomain(domain string) string {
safe, err := idna.ToASCII(strings.NewReplacer(":", "-", "*", "_").Replace(domain))
if err != nil {
log.Fatal("Could not sanitize the domain.",
log.DomainAttr(domain),
log.ErrorAttr(err),
)
}
return safe
}

View file

@ -6,12 +6,10 @@ import (
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/go-acme/lego/v5/certcrypto"
"github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/log"
"golang.org/x/net/idna"
)
type CertificatesReader struct {
@ -78,16 +76,3 @@ func (s *CertificatesReader) ReadCertificate(domain, extension string) ([]*x509.
// The input may be a bundle or a single certificate.
return certcrypto.ParsePEMBundle(content)
}
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
func sanitizedDomain(domain string) string {
safe, err := idna.ToASCII(strings.NewReplacer(":", "-", "*", "_").Replace(domain))
if err != nil {
log.Fatal("Could not sanitize the domain.",
log.DomainAttr(domain),
log.ErrorAttr(err),
)
}
return safe
}

View file

@ -29,8 +29,6 @@ type CertificatesWriterConfig struct {
PFX bool
PFXFormat string
PFXPassword string
Filename string // TODO(ldez): remove
}
// CertificatesWriter a writer of certificate files.
@ -55,8 +53,6 @@ type CertificatesWriter struct {
pfx bool
pfxFormat string
pfxPassword string
filename string // TODO(ldez): remove
}
// NewCertificatesWriter create a new certificates storage writer.
@ -76,7 +72,6 @@ func NewCertificatesWriter(config CertificatesWriterConfig) (*CertificatesWriter
pfx: config.PFX,
pfxPassword: config.PFXPassword,
pfxFormat: config.PFXFormat,
filename: config.Filename,
}, nil
}
@ -247,14 +242,7 @@ func (s *CertificatesWriter) writePFXFile(domain string, certRes *certificate.Re
}
func (s *CertificatesWriter) writeFile(domain, extension string, data []byte) error {
var baseFileName string
if s.filename != "" {
baseFileName = s.filename
} else {
baseFileName = sanitizedDomain(domain)
}
filePath := filepath.Join(s.rootPath, baseFileName+extension)
filePath := filepath.Join(s.rootPath, sanitizedDomain(domain)+extension)
log.Info("Writing file.",
slog.String("filepath", filePath))

View file

@ -32,21 +32,30 @@ func setupClient(cmd *cli.Command, account *storage.Account, keyType certcrypto.
return client
}
func setupAccount(ctx context.Context, cmd *cli.Command, accountsStorage *storage.AccountsStorage) (*storage.Account, certcrypto.KeyType) {
keyType := getKeyType(cmd)
func setupAccount(ctx context.Context, keyType certcrypto.KeyType, accountsStorage *storage.AccountsStorage) *storage.Account {
privateKey := accountsStorage.GetPrivateKey(keyType)
var account *storage.Account
if accountsStorage.ExistsAccountFilePath() {
account = accountsStorage.LoadAccount(ctx, privateKey)
} else {
account = storage.NewAccount(accountsStorage.GetEmail(), privateKey)
return accountsStorage.LoadAccount(ctx, privateKey)
}
return account, keyType
return storage.NewAccount(accountsStorage.GetEmail(), privateKey)
}
func newClient(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
client, err := lego.NewClient(newClientConfig(cmd, acc, keyType))
if err != nil {
log.Fatal("Could not create client.", log.ErrorAttr(err))
}
if client.GetExternalAccountRequired() && !cmd.IsSet(flgEAB) { // TODO(ldez): handle this flag.
log.Fatal(fmt.Sprintf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC))
}
return client
}
func newClientConfig(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyType) *lego.Config {
config := lego.NewConfig(acc)
config.CADirURL = cmd.String(flgServer)
@ -83,16 +92,7 @@ func newClient(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyTy
config.HTTPClient = retryClient.StandardClient()
client, err := lego.NewClient(config)
if err != nil {
log.Fatal("Could not create client.", log.ErrorAttr(err))
}
if client.GetExternalAccountRequired() && !cmd.IsSet(flgEAB) {
log.Fatal(fmt.Sprintf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC))
}
return client
return config
}
// getKeyType the type from which private keys should be generated.

View file

@ -2,51 +2,24 @@ package cmd
import (
"github.com/go-acme/lego/v5/cmd/internal/storage"
"github.com/go-acme/lego/v5/log"
"github.com/urfave/cli/v3"
)
// CertificatesStorage a certificates' storage.
type CertificatesStorage struct {
*storage.CertificatesWriter
*storage.CertificatesReader
}
// newCertificatesStorage create a new certificates storage.
func newCertificatesStorage(cmd *cli.Command) *CertificatesStorage {
basePath := cmd.String(flgPath)
config := storage.CertificatesWriterConfig{
BasePath: basePath,
func newCertificatesWriterConfig(cmd *cli.Command) storage.CertificatesWriterConfig {
return storage.CertificatesWriterConfig{
BasePath: cmd.String(flgPath),
PEM: cmd.Bool(flgPEM),
PFX: cmd.Bool(flgPFX),
PFXFormat: cmd.String(flgPFXPass),
PFXPassword: cmd.String(flgPFXFormat),
Filename: cmd.String(flgFilename),
}
writer, err := storage.NewCertificatesWriter(config)
if err != nil {
log.Fatal("Certificates storage initialization", log.ErrorAttr(err))
}
return &CertificatesStorage{
CertificatesWriter: writer,
CertificatesReader: storage.NewCertificatesReader(basePath),
}
}
// newAccountsStorage Creates a new AccountsStorage.
func newAccountsStorage(cmd *cli.Command) *storage.AccountsStorage {
accountsStorage, err := storage.NewAccountsStorage(storage.AccountsStorageConfig{
func newAccountsStorageConfig(cmd *cli.Command) storage.AccountsStorageConfig {
return storage.AccountsStorageConfig{
Email: cmd.String(flgEmail),
BasePath: cmd.String(flgPath),
Server: cmd.String(flgServer),
UserAgent: getUserAgent(cmd),
})
if err != nil {
log.Fatal("Accounts storage initialization", log.ErrorAttr(err))
}
return accountsStorage
}

View file

@ -32,10 +32,9 @@ USAGE:
lego run
OPTIONS:
--domains string, -d string [ --domains string, -d string ] Add a domain to the process. Can be specified multiple times.
--domains string, -d string [ --domains string, -d string ] Add a domain to the process. Can be specified multiple times or use comma as a separator.
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.
--email string, -m string Email used for registration and recovery contact. [$LEGO_EMAIL]
--csr string, -c string Certificate signing request filename, if an external CSR is to be used.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. [$LEGO_EAB]
--kid string Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID]
--hmac string MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. [$LEGO_EAB_HMAC]
@ -47,7 +46,6 @@ OPTIONS:
--cert.timeout int Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--overall-request-limit int ACME overall requests limit. (default: 18)
--user-agent string Add to the user-agent sent to the CA to identify an application embedding lego-cli
--filename string (deprecated) Filename of the generated certificate.
--path string Directory to use for storing the data. [$LEGO_PATH]
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together.
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. [$LEGO_PFX]
@ -70,16 +68,17 @@ OPTIONS:
--dns.propagation-wait duration By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead. (default: 0s)
--dns.resolvers string [ --dns.resolvers string ] Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination. For DNS-01 challenge verification, the authoritative DNS server is queried directly. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--dns-timeout int Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)
--csr string, -c string Certificate signing request filename, if an external CSR is to be used.
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate.
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.
--not-before time Set the notBefore field in the certificate (RFC3339 format)
--not-after time Set the notAfter field in the certificate (RFC3339 format)
--private-key string Path to private key (in PEM encoding) for the certificate. By default, the private key is generated.
--preferred-chain string If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--profile string If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.
--always-deactivate-authorizations string Force the authorizations to be relinquished even if the certificate request was successful.
--run-hook string Define a hook. The hook is executed when the certificates are effectively created.
--run-hook string Define a hook. The hook is executed only when the certificates are effectively created/renewed.
--run-hook-timeout duration Define the timeout for the hook execution. (default: 2m0s)
--private-key string Path to private key (in PEM encoding) for the certificate. By default, the private key is generated.
--help, -h show help
"""
@ -93,10 +92,9 @@ USAGE:
lego renew
OPTIONS:
--domains string, -d string [ --domains string, -d string ] Add a domain to the process. Can be specified multiple times.
--domains string, -d string [ --domains string, -d string ] Add a domain to the process. Can be specified multiple times or use comma as a separator.
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.
--email string, -m string Email used for registration and recovery contact. [$LEGO_EMAIL]
--csr string, -c string Certificate signing request filename, if an external CSR is to be used.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. [$LEGO_EAB]
--kid string Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID]
--hmac string MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. [$LEGO_EAB_HMAC]
@ -108,7 +106,6 @@ OPTIONS:
--cert.timeout int Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--overall-request-limit int ACME overall requests limit. (default: 18)
--user-agent string Add to the user-agent sent to the CA to identify an application embedding lego-cli
--filename string (deprecated) Filename of the generated certificate.
--path string Directory to use for storing the data. [$LEGO_PATH]
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together.
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. [$LEGO_PFX]
@ -131,11 +128,7 @@ OPTIONS:
--dns.propagation-wait duration By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead. (default: 0s)
--dns.resolvers string [ --dns.resolvers string ] Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination. For DNS-01 challenge verification, the authoritative DNS server is queried directly. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--dns-timeout int Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)
--days int The number of days left on a certificate to renew it. (default: 30)
--dynamic Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5.
--ari-disable Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed.
--ari-wait-to-renew-duration duration The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s)
--reuse-key Used to indicate you want to reuse your current private key for the new certificate.
--csr string, -c string Certificate signing request filename, if an external CSR is to be used.
--no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate.
--must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.
--not-before time Set the notBefore field in the certificate (RFC3339 format)
@ -143,8 +136,13 @@ OPTIONS:
--preferred-chain string If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.
--profile string If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.
--always-deactivate-authorizations string Force the authorizations to be relinquished even if the certificate request was successful.
--renew-hook string Define a hook. The hook is executed only when the certificates are effectively renewed.
--renew-hook string Define a hook. The hook is executed only when the certificates are effectively created/renewed.
--renew-hook-timeout duration Define the timeout for the hook execution. (default: 2m0s)
--days int The number of days left on a certificate to renew it. (default: 30)
--dynamic Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5.
--ari-disable Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed.
--ari-wait-to-renew-duration duration The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s)
--reuse-key Used to indicate you want to reuse your current private key for the new certificate.
--no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way.
--force-cert-domains Check and ensure that the cert's domain list matches those passed in the domains argument.
--help, -h show help
@ -160,47 +158,28 @@ USAGE:
lego revoke
OPTIONS:
--domains string, -d string [ --domains string, -d string ] Add a domain to the process. Can be specified multiple times.
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.
--email string, -m string Email used for registration and recovery contact. [$LEGO_EMAIL]
--csr string, -c string Certificate signing request filename, if an external CSR is to be used.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. [$LEGO_EAB]
--kid string Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID]
--hmac string MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. [$LEGO_EAB_HMAC]
--server string, -s string CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. [$LEGO_SERVER]
--disable-cn Disable the use of the common name in the CSR.
--key-type string, -k string Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384. (default: "ec256")
--http-timeout int Set the HTTP timeout value to a specific value in seconds. (default: 0)
--tls-skip-verify Skip the TLS verification of the ACME server.
--cert.timeout int Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--overall-request-limit int ACME overall requests limit. (default: 18)
--user-agent string Add to the user-agent sent to the CA to identify an application embedding lego-cli
--filename string (deprecated) Filename of the generated certificate.
--path string Directory to use for storing the data. [$LEGO_PATH]
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together.
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. [$LEGO_PFX]
--pfx.pass string The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") [$LEGO_PFX_PASSWORD]
--pfx.format string The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256. (default: "RC2") [$LEGO_PFX_FORMAT]
--http Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges.
--http.port string Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port. (default: ":80")
--http.delay duration Delay between the starts of the HTTP server (use for HTTP-01 based challenges) and the validation of the challenge. (default: 0s)
--http.proxy-header string Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy. (default: "Host")
--http.webroot string Set the webroot folder to use for HTTP-01 based challenges to write directly to the .well-known/acme-challenge file. 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 string [ --http.memcached-host string ] Set the memcached host(s) to use for HTTP-01 based challenges. Challenges will be written to all specified hosts.
--http.s3-bucket string Set the S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.
--tls Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges.
--tls.port string Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port. (default: ":443")
--tls.delay duration Delay between the start of the TLS listener (use for TLSALPN-01 based challenges) and the validation of the challenge. (default: 0s)
--dns string Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.
--dns.disable-cp (deprecated) use dns.propagation-disable-ans instead.
--dns.propagation-disable-ans By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers.
--dns.propagation-rns By setting this flag to true, use all the recursive nameservers to check the propagation of the TXT record.
--dns.propagation-wait duration By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead. (default: 0s)
--dns.resolvers string [ --dns.resolvers string ] Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination. For DNS-01 challenge verification, the authoritative DNS server is queried directly. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--dns-timeout int Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)
--keep, -k Keep the certificates after the revocation instead of archiving them.
--reason uint Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. Valid values are: 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged), 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL), 9 (privilegeWithdrawn), or 10 (aACompromise). (default: 0)
--help, -h show help
--domains string, -d string [ --domains string, -d string ] Add a domain to the process. Can be specified multiple times or use comma as a separator.
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.
--email string, -m string Email used for registration and recovery contact. [$LEGO_EMAIL]
--eab Use External Account Binding for account registration. Requires --kid and --hmac. [$LEGO_EAB]
--kid string Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID]
--hmac string MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. [$LEGO_EAB_HMAC]
--server string, -s string CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. [$LEGO_SERVER]
--disable-cn Disable the use of the common name in the CSR.
--key-type string, -k string Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384. (default: "ec256")
--http-timeout int Set the HTTP timeout value to a specific value in seconds. (default: 0)
--tls-skip-verify Skip the TLS verification of the ACME server.
--cert.timeout int Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates. (default: 30)
--overall-request-limit int ACME overall requests limit. (default: 18)
--user-agent string Add to the user-agent sent to the CA to identify an application embedding lego-cli
--path string Directory to use for storing the data. [$LEGO_PATH]
--pem Generate an additional .pem (base64) file by concatenating the .key and .crt files together.
--pfx Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together. [$LEGO_PFX]
--pfx.pass string The password used to encrypt the .pfx (PCKS#12) file. (default: "changeit") [$LEGO_PFX_PASSWORD]
--pfx.format string The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256. (default: "RC2") [$LEGO_PFX_FORMAT]
--keep, -k Keep the certificates after the revocation instead of archiving them.
--reason uint Identifies the reason for the certificate revocation. See https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1. Valid values are: 0 (unspecified), 1 (keyCompromise), 2 (cACompromise), 3 (affiliationChanged), 4 (superseded), 5 (cessationOfOperation), 6 (certificateHold), 8 (removeFromCRL), 9 (privilegeWithdrawn), or 10 (aACompromise). (default: 0)
--help, -h show help
"""
[[command]]

View file

@ -174,9 +174,7 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) {
"--accept-tos",
"-s", "https://localhost:14000/dir",
"-d", testDomain2,
"--tls",
"--tls.port", ":5001",
"revoke")
)
if err != nil {
t.Fatal(err)
}
@ -204,8 +202,6 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) {
"--accept-tos",
"-s", "https://localhost:14000/dir",
"-d", testDomain4,
"--tls",
"--tls.port", ":5001",
)
if err != nil {
t.Fatal(err)