diff --git a/cmd/accounts_storage.go b/cmd/accounts_storage.go index 05cd2372..b3e4986d 100644 --- a/cmd/accounts_storage.go +++ b/cmd/accounts_storage.go @@ -71,12 +71,12 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage { // TODO: move to account struct? Currently MUST pass email. email := getEmail(ctx) - serverURL, err := url.Parse(ctx.String("server")) + serverURL, err := url.Parse(ctx.String(flgServer)) if err != nil { log.Fatal(err) } - rootPath := filepath.Join(ctx.String("path"), baseAccountsRootFolderName) + rootPath := filepath.Join(ctx.String(flgPath), baseAccountsRootFolderName) serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host) accountsPath := filepath.Join(rootPath, serverPath) rootUserPath := filepath.Join(accountsPath, email) @@ -224,7 +224,7 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) { func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) { // couldn't load account but got a key. Try to look the account up. config := lego.NewConfig(&Account{key: privateKey}) - config.CADirURL = ctx.String("server") + config.CADirURL = ctx.String(flgServer) config.UserAgent = getUserAgent(ctx) client, err := lego.NewClient(config) diff --git a/cmd/certs_storage.go b/cmd/certs_storage.go index 46f04bfe..f9bcdade 100644 --- a/cmd/certs_storage.go +++ b/cmd/certs_storage.go @@ -61,7 +61,7 @@ type CertificatesStorage struct { // NewCertificatesStorage create a new certificates storage. func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage { - pfxFormat := ctx.String("pfx.format") + pfxFormat := ctx.String(flgPFXFormat) switch pfxFormat { case "DES", "RC2", "SHA256": @@ -70,13 +70,13 @@ func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage { } return &CertificatesStorage{ - rootPath: filepath.Join(ctx.String("path"), baseCertificatesFolderName), - archivePath: filepath.Join(ctx.String("path"), baseArchivesFolderName), - pem: ctx.Bool("pem"), - pfx: ctx.Bool("pfx"), - pfxPassword: ctx.String("pfx.pass"), + rootPath: filepath.Join(ctx.String(flgPath), baseCertificatesFolderName), + archivePath: filepath.Join(ctx.String(flgPath), baseArchivesFolderName), + pem: ctx.Bool(flgPEM), + pfx: ctx.Bool(flgPFX), + pfxPassword: ctx.String(flgPFXPass), pfxFormat: pfxFormat, - filename: ctx.String("filename"), + filename: ctx.String(flgFilename), } } diff --git a/cmd/cmd_before.go b/cmd/cmd_before.go index 61ffcb97..04d8d325 100644 --- a/cmd/cmd_before.go +++ b/cmd/cmd_before.go @@ -6,17 +6,17 @@ import ( ) func Before(ctx *cli.Context) error { - if ctx.String("path") == "" { - log.Fatal("Could not determine current working directory. Please pass --path.") + if ctx.String(flgPath) == "" { + log.Fatalf("Could not determine current working directory. Please pass --%s.", flgPath) } - err := createNonExistingFolder(ctx.String("path")) + err := createNonExistingFolder(ctx.String(flgPath)) if err != nil { log.Fatalf("Could not check/create path: %v", err) } - if ctx.String("server") == "" { - log.Fatal("Could not determine current working server. Please pass --server.") + if ctx.String(flgServer) == "" { + log.Fatalf("Could not determine current working server. Please pass --%s.", flgServer) } return nil diff --git a/cmd/cmd_dnshelp.go b/cmd/cmd_dnshelp.go index e38e0a38..1a61cac8 100644 --- a/cmd/cmd_dnshelp.go +++ b/cmd/cmd_dnshelp.go @@ -9,6 +9,8 @@ import ( "github.com/urfave/cli/v2" ) +const flgCode = "code" + func createDNSHelp() *cli.Command { return &cli.Command{ Name: "dnshelp", @@ -16,7 +18,7 @@ func createDNSHelp() *cli.Command { Action: dnsHelp, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "code", + Name: flgCode, Aliases: []string{"c"}, Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()), }, @@ -25,7 +27,7 @@ func createDNSHelp() *cli.Command { } func dnsHelp(ctx *cli.Context) error { - code := ctx.String("code") + code := ctx.String(flgCode) if code == "" { w := tabwriter.NewWriter(ctx.App.Writer, 0, 0, 2, ' ', 0) ew := &errWriter{w: w} diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index 71daaf88..bf7b232d 100644 --- a/cmd/cmd_list.go +++ b/cmd/cmd_list.go @@ -12,6 +12,11 @@ import ( "github.com/urfave/cli/v2" ) +const ( + flgAccounts = "accounts" + flgNames = "names" +) + func createList() *cli.Command { return &cli.Command{ Name: "list", @@ -19,18 +24,18 @@ func createList() *cli.Command { Action: list, Flags: []cli.Flag{ &cli.BoolFlag{ - Name: "accounts", + Name: flgAccounts, Aliases: []string{"a"}, Usage: "Display accounts.", }, &cli.BoolFlag{ - Name: "names", + Name: flgNames, Aliases: []string{"n"}, Usage: "Display certificate common names only.", }, // fake email, needed by NewAccountsStorage &cli.StringFlag{ - Name: "email", + Name: flgEmail, Value: "unknown", Hidden: true, }, @@ -39,7 +44,7 @@ func createList() *cli.Command { } func list(ctx *cli.Context) error { - if ctx.Bool("accounts") && !ctx.Bool("names") { + if ctx.Bool(flgAccounts) && !ctx.Bool(flgNames) { if err := listAccount(ctx); err != nil { return err } @@ -56,7 +61,7 @@ func listCertificates(ctx *cli.Context) error { return err } - names := ctx.Bool("names") + names := ctx.Bool(flgNames) if len(matches) == 0 { if !names { @@ -70,7 +75,7 @@ func listCertificates(ctx *cli.Context) error { } for _, filename := range matches { - if strings.HasSuffix(filename, ".issuer.crt") { + if strings.HasSuffix(filename, issuerExt) { continue } diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index ed6d728e..1d0a0407 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -17,6 +17,16 @@ import ( "github.com/urfave/cli/v2" ) +// Flag names. +const ( + flgDays = "days" + flgARIEnable = "ari-enable" + flgARIWaitToRenewDuration = "ari-wait-to-renew-duration" + flgReuseKey = "reuse-key" + flgRenewHook = "renew-hook" + flgNoRandomSleep = "no-random-sleep" +) + const ( renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL" renewEnvCertDomain = "LEGO_CERT_DOMAIN" @@ -34,68 +44,68 @@ func createRenew() *cli.Command { Action: renew, Before: func(ctx *cli.Context) error { // we require either domains or csr, but not both - hasDomains := len(ctx.StringSlice("domains")) > 0 - hasCsr := ctx.String("csr") != "" + hasDomains := len(ctx.StringSlice(flgDomains)) > 0 + hasCsr := ctx.String(flgCSR) != "" if hasDomains && hasCsr { - log.Fatal("Please specify either --domains/-d or --csr/-c, but not both") + log.Fatal("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR) } if !hasDomains && !hasCsr { - log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)") + log.Fatal("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR) } return nil }, Flags: []cli.Flag{ &cli.IntFlag{ - Name: "days", + Name: flgDays, Value: 30, Usage: "The number of days left on a certificate to renew it.", }, &cli.BoolFlag{ - Name: "ari-enable", + Name: flgARIEnable, Usage: "Use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed.", }, &cli.DurationFlag{ - Name: "ari-wait-to-renew-duration", + Name: flgARIWaitToRenewDuration, Usage: "The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint.", }, &cli.BoolFlag{ - Name: "reuse-key", + Name: flgReuseKey, Usage: "Used to indicate you want to reuse your current private key for the new certificate.", }, &cli.BoolFlag{ - Name: "no-bundle", + Name: flgNoBundle, Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.", }, &cli.BoolFlag{ - Name: "must-staple", + 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: "not-before", + Name: flgNotBefore, Usage: "Set the notBefore field in the certificate (RFC3339 format)", Layout: time.RFC3339, }, &cli.TimestampFlag{ - Name: "not-after", + Name: flgNotAfter, Usage: "Set the notAfter field in the certificate (RFC3339 format)", Layout: time.RFC3339, }, &cli.StringFlag{ - Name: "preferred-chain", + 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: "always-deactivate-authorizations", + Name: flgAlwaysDeactivateAuthorizations, Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", }, &cli.StringFlag{ - Name: "renew-hook", + Name: flgRenewHook, Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.", }, &cli.BoolFlag{ - Name: "no-random-sleep", + 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.", }, @@ -113,12 +123,12 @@ func renew(ctx *cli.Context) error { certsStorage := NewCertificatesStorage(ctx) - bundle := !ctx.Bool("no-bundle") + bundle := !ctx.Bool(flgNoBundle) meta := map[string]string{renewEnvAccountEmail: account.Email} // CSR - if ctx.IsSet("csr") { + if ctx.IsSet(flgCSR) { return renewForCSR(ctx, client, certsStorage, bundle, meta) } @@ -127,13 +137,13 @@ func renew(ctx *cli.Context) error { } func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { - domains := ctx.StringSlice("domains") + domains := ctx.StringSlice(flgDomains) domain := domains[0] // load the cert resource from files. // We store the certificate, private key and metadata in different files // as web servers would not be able to work with a combined file. - certificates, err := certsStorage.ReadCertificate(domain, ".crt") + certificates, err := certsStorage.ReadCertificate(domain, certExt) if err != nil { log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err) } @@ -141,7 +151,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif cert := certificates[0] var ariRenewalTime *time.Time - if ctx.Bool("ari-enable") { + if ctx.Bool(flgARIEnable) { ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client) if ariRenewalTime != nil { now := time.Now().UTC() @@ -153,7 +163,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif } } - if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int("days")) { + if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) { return nil } @@ -164,8 +174,8 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif certDomains := certcrypto.ExtractDomains(cert) var privateKey crypto.PrivateKey - if ctx.Bool("reuse-key") { - keyBytes, errR := certsStorage.ReadFile(domain, ".key") + if ctx.Bool(flgReuseKey) { + keyBytes, errR := certsStorage.ReadFile(domain, keyExt) if errR != nil { log.Fatalf("Error while loading the private key for domain %s\n\t%v", domain, errR) } @@ -178,7 +188,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif // https://github.com/go-acme/lego/issues/1656 // https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L435-L440 - if !isatty.IsTerminal(os.Stdout.Fd()) && !ctx.Bool("no-random-sleep") { + if !isatty.IsTerminal(os.Stdout.Fd()) && !ctx.Bool(flgNoRandomSleep) { // https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L472 const jitter = 8 * time.Minute rnd := rand.New(rand.NewSource(time.Now().UnixNano())) @@ -191,15 +201,15 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif request := certificate.ObtainRequest{ Domains: merge(certDomains, domains), PrivateKey: privateKey, - MustStaple: ctx.Bool("must-staple"), - NotBefore: getTime(ctx, "not-before"), - NotAfter: getTime(ctx, "not-after"), + MustStaple: ctx.Bool(flgMustStaple), + NotBefore: getTime(ctx, flgNotBefore), + NotAfter: getTime(ctx, flgNotAfter), Bundle: bundle, - PreferredChain: ctx.String("preferred-chain"), - AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), + PreferredChain: ctx.String(flgPreferredChain), + AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } - if ctx.Bool("ari-enable") { + if ctx.Bool(flgARIEnable) { request.ReplacesCertID, err = certificate.MakeARICertID(cert) if err != nil { log.Fatalf("Error while construction the ARI CertID for domain %s\n\t%v", domain, err) @@ -215,11 +225,11 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif addPathToMetadata(meta, domain, certRes, certsStorage) - return launchHook(ctx.String("renew-hook"), meta) + return launchHook(ctx.String(flgRenewHook), meta) } func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { - csr, err := readCSRFile(ctx.String("csr")) + csr, err := readCSRFile(ctx.String(flgCSR)) if err != nil { log.Fatal(err) } @@ -232,7 +242,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat // load the cert resource from files. // We store the certificate, private key and metadata in different files // as web servers would not be able to work with a combined file. - certificates, err := certsStorage.ReadCertificate(domain, ".crt") + certificates, err := certsStorage.ReadCertificate(domain, certExt) if err != nil { log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err) } @@ -240,7 +250,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat cert := certificates[0] var ariRenewalTime *time.Time - if ctx.Bool("ari-enable") { + if ctx.Bool(flgARIEnable) { ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client) if ariRenewalTime != nil { now := time.Now().UTC() @@ -252,7 +262,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat } } - if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int("days")) { + if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) { return nil } @@ -262,14 +272,14 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat request := certificate.ObtainForCSRRequest{ CSR: csr, - NotBefore: getTime(ctx, "not-before"), - NotAfter: getTime(ctx, "not-after"), + NotBefore: getTime(ctx, flgNotBefore), + NotAfter: getTime(ctx, flgNotAfter), Bundle: bundle, - PreferredChain: ctx.String("preferred-chain"), - AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), + PreferredChain: ctx.String(flgPreferredChain), + AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } - if ctx.Bool("ari-enable") { + if ctx.Bool(flgARIEnable) { request.ReplacesCertID, err = certificate.MakeARICertID(cert) if err != nil { log.Fatalf("Error while construction the ARI CertID for domain %s\n\t%v", domain, err) @@ -285,7 +295,7 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat addPathToMetadata(meta, domain, certRes, certsStorage) - return launchHook(ctx.String("renew-hook"), meta) + return launchHook(ctx.String(flgRenewHook), meta) } func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool { @@ -323,7 +333,7 @@ func getARIRenewalTime(ctx *cli.Context, cert *x509.Certificate, domain string, } now := time.Now().UTC() - renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration("ari-wait-to-renew-duration")) + renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration(flgARIWaitToRenewDuration)) if renewalTime == nil { log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is not needed", domain) return nil diff --git a/cmd/cmd_revoke.go b/cmd/cmd_revoke.go index cd95978d..2ecfd301 100644 --- a/cmd/cmd_revoke.go +++ b/cmd/cmd_revoke.go @@ -6,6 +6,12 @@ import ( "github.com/urfave/cli/v2" ) +// Flag names. +const ( + flgKeep = "keep" + flgReason = "reason" +) + func createRevoke() *cli.Command { return &cli.Command{ Name: "revoke", @@ -13,12 +19,12 @@ func createRevoke() *cli.Command { Action: revoke, Flags: []cli.Flag{ &cli.BoolFlag{ - Name: "keep", + Name: flgKeep, Aliases: []string{"k"}, Usage: "Keep the certificates after the revocation instead of archiving them.", }, &cli.UintFlag{ - Name: "reason", + 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:" + @@ -41,15 +47,15 @@ func revoke(ctx *cli.Context) error { certsStorage := NewCertificatesStorage(ctx) certsStorage.CreateRootFolder() - for _, domain := range ctx.StringSlice("domains") { + for _, domain := range ctx.StringSlice(flgDomains) { log.Printf("Trying to revoke certificate for domain %s", domain) - certBytes, err := certsStorage.ReadFile(domain, ".crt") + certBytes, err := certsStorage.ReadFile(domain, certExt) if err != nil { log.Fatalf("Error while revoking the certificate for domain %s\n\t%v", domain, err) } - reason := ctx.Uint("reason") + reason := ctx.Uint(flgReason) err = client.Certificate.RevokeWithReason(certBytes, &reason) if err != nil { @@ -58,7 +64,7 @@ func revoke(ctx *cli.Context) error { log.Println("Certificate was revoked.") - if ctx.Bool("keep") { + if ctx.Bool(flgKeep) { return nil } diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index c5d13461..a1d4cd51 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -14,14 +14,25 @@ import ( "github.com/urfave/cli/v2" ) +// Flag names. +const ( + flgNoBundle = "no-bundle" + flgMustStaple = "must-staple" + flgNotBefore = "not-before" + flgNotAfter = "not-after" + flgPreferredChain = "preferred-chain" + flgAlwaysDeactivateAuthorizations = "always-deactivate-authorizations" + flgRunHook = "run-hook" +) + func createRun() *cli.Command { return &cli.Command{ Name: "run", Usage: "Register an account, then create and install a certificate", Before: func(ctx *cli.Context) error { // we require either domains or csr, but not both - hasDomains := len(ctx.StringSlice("domains")) > 0 - hasCsr := ctx.String("csr") != "" + hasDomains := len(ctx.StringSlice(flgDomains)) > 0 + hasCsr := ctx.String(flgCSR) != "" if hasDomains && hasCsr { log.Fatal("Please specify either --domains/-d or --csr/-c, but not both") } @@ -33,35 +44,35 @@ func createRun() *cli.Command { Action: run, Flags: []cli.Flag{ &cli.BoolFlag{ - Name: "no-bundle", + Name: flgNoBundle, Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.", }, &cli.BoolFlag{ - Name: "must-staple", + 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: "not-before", + Name: flgNotBefore, Usage: "Set the notBefore field in the certificate (RFC3339 format)", Layout: time.RFC3339, }, &cli.TimestampFlag{ - Name: "not-after", + Name: flgNotAfter, Usage: "Set the notAfter field in the certificate (RFC3339 format)", Layout: time.RFC3339, }, &cli.StringFlag{ - Name: "preferred-chain", + 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: "always-deactivate-authorizations", + Name: flgAlwaysDeactivateAuthorizations, Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", }, &cli.StringFlag{ - Name: "run-hook", + Name: flgRunHook, Usage: "Define a hook. The hook is executed when the certificates are effectively created.", }, }, @@ -117,12 +128,12 @@ func run(ctx *cli.Context) error { addPathToMetadata(meta, cert.Domain, cert, certsStorage) - return launchHook(ctx.String("run-hook"), meta) + return launchHook(ctx.String(flgRunHook), meta) } func handleTOS(ctx *cli.Context, client *lego.Client) bool { // Check for a global accept override - if ctx.Bool("accept-tos") { + if ctx.Bool(flgAcceptTOS) { return true } @@ -154,12 +165,12 @@ func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, er log.Fatal("You did not accept the TOS. Unable to proceed.") } - if ctx.Bool("eab") { - kid := ctx.String("kid") - hmacEncoded := ctx.String("hmac") + if ctx.Bool(flgEAB) { + kid := ctx.String(flgKID) + hmacEncoded := ctx.String(flgHMAC) if kid == "" || hmacEncoded == "" { - log.Fatalf("Requires arguments --kid and --hmac.") + log.Fatalf("Requires arguments --%s and --%s.", flgKID, flgHMAC) } return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ @@ -173,25 +184,25 @@ func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, er } func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Resource, error) { - bundle := !ctx.Bool("no-bundle") + bundle := !ctx.Bool(flgNoBundle) - domains := ctx.StringSlice("domains") + domains := ctx.StringSlice(flgDomains) if len(domains) > 0 { // obtain a certificate, generating a new private key request := certificate.ObtainRequest{ Domains: domains, Bundle: bundle, - MustStaple: ctx.Bool("must-staple"), - PreferredChain: ctx.String("preferred-chain"), - AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), + MustStaple: ctx.Bool(flgMustStaple), + PreferredChain: ctx.String(flgPreferredChain), + AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } - notBefore := ctx.Timestamp("not-before") + notBefore := ctx.Timestamp(flgNotBefore) if notBefore != nil { request.NotBefore = *notBefore } - notAfter := ctx.Timestamp("not-after") + notAfter := ctx.Timestamp(flgNotAfter) if notAfter != nil { request.NotAfter = *notAfter } @@ -200,7 +211,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso } // read the CSR - csr, err := readCSRFile(ctx.String("csr")) + csr, err := readCSRFile(ctx.String(flgCSR)) if err != nil { return nil, err } @@ -208,11 +219,11 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso // obtain a certificate for this CSR request := certificate.ObtainForCSRRequest{ CSR: csr, - NotBefore: getTime(ctx, "not-before"), - NotAfter: getTime(ctx, "not-after"), + NotBefore: getTime(ctx, flgNotBefore), + NotAfter: getTime(ctx, flgNotAfter), Bundle: bundle, - PreferredChain: ctx.String("preferred-chain"), - AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), + PreferredChain: ctx.String(flgPreferredChain), + AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } return client.Certificate.ObtainForCSR(request) diff --git a/cmd/flags.go b/cmd/flags.go index dd9fb746..03d34030 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -9,163 +9,199 @@ import ( "software.sslmate.com/src/go-pkcs12" ) +// Flag names. +const ( + flgDomains = "domains" + flgServer = "server" + flgAcceptTOS = "accept-tos" + flgEmail = "email" + flgCSR = "csr" + flgEAB = "eab" + flgKID = "kid" + flgHMAC = "hmac" + flgKeyType = "key-type" + flgFilename = "filename" + flgPath = "path" + flgHTTP = "http" + flgHTTPPort = "http.port" + flgHTTPProxyHeader = "http.proxy-header" + flgHTTPWebroot = "http.webroot" + flgHTTPMemcachedHost = "http.memcached-host" + flgHTTPS3Bucket = "http.s3-bucket" + flgTLS = "tls" + flgTLSPort = "tls.port" + flgDNS = "dns" + flgDNSDisableCP = "dns.disable-cp" + flgDNSPropagationWait = "dns.propagation-wait" + flgDNSResolvers = "dns.resolvers" + flgHTTPTimeout = "http-timeout" + flgDNSTimeout = "dns-timeout" + flgPEM = "pem" + flgPFX = "pfx" + flgPFXPass = "pfx.pass" + flgPFXFormat = "pfx.format" + flgCertTimeout = "cert.timeout" + flgOverallRequestLimit = "overall-request-limit" + flgUserAgent = "user-agent" +) + func CreateFlags(defaultPath string) []cli.Flag { return []cli.Flag{ &cli.StringSliceFlag{ - Name: "domains", + Name: flgDomains, Aliases: []string{"d"}, Usage: "Add a domain to the process. Can be specified multiple times.", }, &cli.StringFlag{ - Name: "server", + Name: flgServer, Aliases: []string{"s"}, EnvVars: []string{"LEGO_SERVER"}, Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.", Value: lego.LEDirectoryProduction, }, &cli.BoolFlag{ - Name: "accept-tos", + Name: flgAcceptTOS, Aliases: []string{"a"}, Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.", }, &cli.StringFlag{ - Name: "email", + Name: flgEmail, Aliases: []string{"m"}, Usage: "Email used for registration and recovery contact.", }, &cli.StringFlag{ - Name: "csr", + Name: flgCSR, Aliases: []string{"c"}, Usage: "Certificate signing request filename, if an external CSR is to be used.", }, &cli.BoolFlag{ - Name: "eab", + Name: flgEAB, EnvVars: []string{"LEGO_EAB"}, Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.", }, &cli.StringFlag{ - Name: "kid", + Name: flgKID, EnvVars: []string{"LEGO_EAB_KID"}, Usage: "Key identifier from External CA. Used for External Account Binding.", }, &cli.StringFlag{ - Name: "hmac", + Name: flgHMAC, EnvVars: []string{"LEGO_EAB_HMAC"}, Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.", }, &cli.StringFlag{ - Name: "key-type", + Name: flgKeyType, Aliases: []string{"k"}, Value: "ec256", Usage: "Key type to use for private keys. Supported: rsa2048, rsa3072, rsa4096, rsa8192, ec256, ec384.", }, &cli.StringFlag{ - Name: "filename", + Name: flgFilename, Usage: "(deprecated) Filename of the generated certificate.", }, &cli.StringFlag{ - Name: "path", + Name: flgPath, EnvVars: []string{"LEGO_PATH"}, Usage: "Directory to use for storing the data.", Value: defaultPath, }, &cli.BoolFlag{ - Name: "http", + Name: flgHTTP, Usage: "Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges.", }, &cli.StringFlag{ - Name: "http.port", + Name: flgHTTPPort, Usage: "Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port.", Value: ":80", }, &cli.StringFlag{ - Name: "http.proxy-header", + Name: flgHTTPProxyHeader, Usage: "Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy.", Value: "Host", }, &cli.StringFlag{ - Name: "http.webroot", + Name: flgHTTPWebroot, Usage: "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", }, &cli.StringSliceFlag{ - Name: "http.memcached-host", + Name: flgHTTPMemcachedHost, Usage: "Set the memcached host(s) to use for HTTP-01 based challenges. Challenges will be written to all specified hosts.", }, &cli.StringFlag{ - Name: "http.s3-bucket", + Name: flgHTTPS3Bucket, Usage: "Set the S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.", }, &cli.BoolFlag{ - Name: "tls", + Name: flgTLS, Usage: "Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges.", }, &cli.StringFlag{ - Name: "tls.port", + Name: flgTLSPort, Usage: "Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port.", Value: ":443", }, &cli.StringFlag{ - Name: "dns", + Name: flgDNS, Usage: "Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.", }, &cli.BoolFlag{ - Name: "dns.disable-cp", + Name: flgDNSDisableCP, Usage: "By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers.", }, &cli.DurationFlag{ - Name: "dns.propagation-wait", + Name: flgDNSPropagationWait, Usage: "By setting this flag, disables all the propagation checks and uses a wait duration instead.", }, &cli.StringSliceFlag{ - Name: "dns.resolvers", + Name: flgDNSResolvers, Usage: "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.", }, &cli.IntFlag{ - Name: "http-timeout", + Name: flgHTTPTimeout, Usage: "Set the HTTP timeout value to a specific value in seconds.", }, &cli.IntFlag{ - Name: "dns-timeout", + Name: flgDNSTimeout, Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries.", Value: 10, }, &cli.BoolFlag{ - Name: "pem", + Name: flgPEM, Usage: "Generate an additional .pem (base64) file by concatenating the .key and .crt files together.", }, &cli.BoolFlag{ - Name: "pfx", + Name: flgPFX, Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.", EnvVars: []string{"LEGO_PFX"}, }, &cli.StringFlag{ - Name: "pfx.pass", + Name: flgPFXPass, Usage: "The password used to encrypt the .pfx (PCKS#12) file.", Value: pkcs12.DefaultPassword, EnvVars: []string{"LEGO_PFX_PASSWORD"}, }, &cli.StringFlag{ - Name: "pfx.format", + Name: flgPFXFormat, Usage: "The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256.", Value: "RC2", EnvVars: []string{"LEGO_PFX_FORMAT"}, }, &cli.IntFlag{ - Name: "cert.timeout", + Name: flgCertTimeout, Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.", Value: 30, }, &cli.IntFlag{ - Name: "overall-request-limit", + Name: flgOverallRequestLimit, Usage: "ACME overall requests limit.", Value: certificate.DefaultOverallRequestLimit, }, &cli.StringFlag{ - Name: "user-agent", + Name: flgUserAgent, Usage: "Add to the user-agent sent to the CA to identify an application embedding lego-cli", }, } diff --git a/cmd/setup.go b/cmd/setup.go index d2defb13..00a7f2f2 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -35,17 +35,17 @@ func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego. func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client { config := lego.NewConfig(acc) - config.CADirURL = ctx.String("server") + config.CADirURL = ctx.String(flgServer) config.Certificate = lego.CertificateConfig{ KeyType: keyType, - Timeout: time.Duration(ctx.Int("cert.timeout")) * time.Second, - OverallRequestLimit: ctx.Int("overall-request-limit"), + Timeout: time.Duration(ctx.Int(flgCertTimeout)) * time.Second, + OverallRequestLimit: ctx.Int(flgOverallRequestLimit), } config.UserAgent = getUserAgent(ctx) - if ctx.IsSet("http-timeout") { - config.HTTPClient.Timeout = time.Duration(ctx.Int("http-timeout")) * time.Second + if ctx.IsSet(flgHTTPTimeout) { + config.HTTPClient.Timeout = time.Duration(ctx.Int(flgHTTPTimeout)) * time.Second } client, err := lego.NewClient(config) @@ -53,8 +53,8 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy log.Fatalf("Could not create client: %v", err) } - if client.GetExternalAccountRequired() && !ctx.IsSet("eab") { - log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.") + if client.GetExternalAccountRequired() && !ctx.IsSet(flgEAB) { + log.Fatalf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC) } return client @@ -62,7 +62,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy // getKeyType the type from which private keys should be generated. func getKeyType(ctx *cli.Context) certcrypto.KeyType { - keyType := ctx.String("key-type") + keyType := ctx.String(flgKeyType) switch strings.ToUpper(keyType) { case "RSA2048": return certcrypto.RSA2048 @@ -83,15 +83,15 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType { } func getEmail(ctx *cli.Context) string { - email := ctx.String("email") + email := ctx.String(flgEmail) if email == "" { - log.Fatal("You have to pass an account (email address) to the program using --email or -m") + log.Fatalf("You have to pass an account (email address) to the program using --%s or -m", flgEmail) } return email } func getUserAgent(ctx *cli.Context) string { - return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String("user-agent"), ctx.App.Version)) + return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String(flgUserAgent), ctx.App.Version)) } func createNonExistingFolder(path string) error { diff --git a/cmd/setup_challenges.go b/cmd/setup_challenges.go index 271779f9..f5444c7c 100644 --- a/cmd/setup_challenges.go +++ b/cmd/setup_challenges.go @@ -1,7 +1,7 @@ package cmd import ( - "errors" + "fmt" "net" "strings" "time" @@ -20,25 +20,25 @@ import ( ) func setupChallenges(ctx *cli.Context, client *lego.Client) { - if !ctx.Bool("http") && !ctx.Bool("tls") && !ctx.IsSet("dns") { - log.Fatal("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`.") + if !ctx.Bool(flgHTTP) && !ctx.Bool(flgTLS) && !ctx.IsSet(flgDNS) { + log.Fatalf("No challenge selected. You must specify at least one challenge: `--%s`, `--%s`, `--%s`.", flgHTTP, flgTLS, flgDNS) } - if ctx.Bool("http") { + if ctx.Bool(flgHTTP) { err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx)) if err != nil { log.Fatal(err) } } - if ctx.Bool("tls") { + if ctx.Bool(flgTLS) { err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx)) if err != nil { log.Fatal(err) } } - if ctx.IsSet("dns") { + if ctx.IsSet(flgDNS) { err := setupDNS(ctx, client) if err != nil { log.Fatal(err) @@ -49,28 +49,28 @@ func setupChallenges(ctx *cli.Context, client *lego.Client) { //nolint:gocyclo // the complexity is expected. func setupHTTPProvider(ctx *cli.Context) challenge.Provider { switch { - case ctx.IsSet("http.webroot"): - ps, err := webroot.NewHTTPProvider(ctx.String("http.webroot")) + case ctx.IsSet(flgHTTPWebroot): + ps, err := webroot.NewHTTPProvider(ctx.String(flgHTTPWebroot)) if err != nil { log.Fatal(err) } return ps - case ctx.IsSet("http.memcached-host"): - ps, err := memcached.NewMemcachedProvider(ctx.StringSlice("http.memcached-host")) + case ctx.IsSet(flgHTTPMemcachedHost): + ps, err := memcached.NewMemcachedProvider(ctx.StringSlice(flgHTTPMemcachedHost)) if err != nil { log.Fatal(err) } return ps - case ctx.IsSet("http.s3-bucket"): - ps, err := s3.NewHTTPProvider(ctx.String("http.s3-bucket")) + case ctx.IsSet(flgHTTPS3Bucket): + ps, err := s3.NewHTTPProvider(ctx.String(flgHTTPS3Bucket)) if err != nil { log.Fatal(err) } return ps - case ctx.IsSet("http.port"): - iface := ctx.String("http.port") + case ctx.IsSet(flgHTTPPort): + iface := ctx.String(flgHTTPPort) if !strings.Contains(iface, ":") { - log.Fatalf("The --http switch only accepts interface:port or :port for its argument.") + log.Fatalf("The --%s switch only accepts interface:port or :port for its argument.", flgHTTPPort) } host, port, err := net.SplitHostPort(iface) @@ -79,13 +79,13 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { } srv := http01.NewProviderServer(host, port) - if header := ctx.String("http.proxy-header"); header != "" { + if header := ctx.String(flgHTTPProxyHeader); header != "" { srv.SetProxyHeader(header) } return srv - case ctx.Bool("http"): + case ctx.Bool(flgHTTP): srv := http01.NewProviderServer("", "") - if header := ctx.String("http.proxy-header"); header != "" { + if header := ctx.String(flgHTTPProxyHeader); header != "" { srv.SetProxyHeader(header) } return srv @@ -97,10 +97,10 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { func setupTLSProvider(ctx *cli.Context) challenge.Provider { switch { - case ctx.IsSet("tls.port"): - iface := ctx.String("tls.port") + case ctx.IsSet(flgTLSPort): + iface := ctx.String(flgTLSPort) if !strings.Contains(iface, ":") { - log.Fatalf("The --tls switch only accepts interface:port or :port for its argument.") + log.Fatalf("The --%s switch only accepts interface:port or :port for its argument.", flgTLSPort) } host, port, err := net.SplitHostPort(iface) @@ -109,7 +109,7 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider { } return tlsalpn01.NewProviderServer(host, port) - case ctx.Bool("tls"): + case ctx.Bool(flgTLS): return tlsalpn01.NewProviderServer("", "") default: log.Fatal("Invalid HTTP challenge options.") @@ -118,38 +118,38 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider { } func setupDNS(ctx *cli.Context, client *lego.Client) error { - if ctx.IsSet("dns.disable-cp") && ctx.Bool("dns.disable-cp") && ctx.IsSet("dns.propagation-wait") { - return errors.New("'dns.disable-cp' and 'dns.propagation-wait' are mutually exclusive") + if ctx.IsSet(flgDNSDisableCP) && ctx.Bool(flgDNSDisableCP) && ctx.IsSet(flgDNSPropagationWait) { + return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSDisableCP, flgDNSPropagationWait) } - wait := ctx.Duration("dns.propagation-wait") + wait := ctx.Duration(flgDNSPropagationWait) if wait < 0 { - return errors.New("'dns.propagation-wait' cannot be negative") + return fmt.Errorf("'%s' cannot be negative", flgDNSPropagationWait) } - provider, err := dns.NewDNSChallengeProviderByName(ctx.String("dns")) + provider, err := dns.NewDNSChallengeProviderByName(ctx.String(flgDNS)) if err != nil { return err } - servers := ctx.StringSlice("dns.resolvers") + servers := ctx.StringSlice(flgDNSResolvers) err = client.Challenge.SetDNS01Provider(provider, dns01.CondOption(len(servers) > 0, - dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice("dns.resolvers")))), + dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice(flgDNSResolvers)))), - dns01.CondOption(ctx.Bool("dns.disable-cp"), + dns01.CondOption(ctx.Bool(flgDNSDisableCP), dns01.DisableCompletePropagationRequirement()), - dns01.CondOption(ctx.IsSet("dns.propagation-wait"), dns01.WrapPreCheck( + dns01.CondOption(ctx.IsSet(flgDNSPropagationWait), dns01.WrapPreCheck( func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) { time.Sleep(wait) return true, nil }, )), - dns01.CondOption(ctx.IsSet("dns-timeout"), - dns01.AddDNSTimeout(time.Duration(ctx.Int("dns-timeout"))*time.Second)), + dns01.CondOption(ctx.IsSet(flgDNSTimeout), + dns01.AddDNSTimeout(time.Duration(ctx.Int(flgDNSTimeout))*time.Second)), ) return err