2019-03-11 18:56:48 +02:00
|
|
|
package cmd
|
2018-12-06 23:50:17 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
2019-07-30 21:19:32 +02:00
|
|
|
"github.com/go-acme/lego/v3/certificate"
|
|
|
|
"github.com/go-acme/lego/v3/lego"
|
|
|
|
"github.com/go-acme/lego/v3/log"
|
|
|
|
"github.com/go-acme/lego/v3/registration"
|
2018-12-06 23:50:17 +02:00
|
|
|
"github.com/urfave/cli"
|
|
|
|
)
|
|
|
|
|
|
|
|
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.GlobalStringSlice("domains")) > 0
|
|
|
|
hasCsr := len(ctx.GlobalString("csr")) > 0
|
|
|
|
if hasDomains && hasCsr {
|
|
|
|
log.Fatal("Please specify either --domains/-d or --csr/-c, but not both")
|
|
|
|
}
|
|
|
|
if !hasDomains && !hasCsr {
|
|
|
|
log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
Action: run,
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "no-bundle",
|
|
|
|
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
|
|
|
},
|
|
|
|
cli.BoolFlag{
|
|
|
|
Name: "must-staple",
|
|
|
|
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 19:23:17 +02:00
|
|
|
const rootPathWarningMessage = `!!!! HEADS UP !!!!
|
|
|
|
|
|
|
|
Your account credentials have been saved in your Let's Encrypt
|
|
|
|
configuration directory at "%s".
|
|
|
|
|
|
|
|
You should make a secure backup of this folder now. This
|
|
|
|
configuration directory will also contain certificates and
|
|
|
|
private keys obtained from Let's Encrypt so making regular
|
|
|
|
backups of this folder is ideal.
|
|
|
|
`
|
|
|
|
|
2018-12-06 23:50:17 +02:00
|
|
|
func run(ctx *cli.Context) error {
|
|
|
|
accountsStorage := NewAccountsStorage(ctx)
|
|
|
|
|
|
|
|
account, client := setup(ctx, accountsStorage)
|
2019-03-11 17:54:35 +02:00
|
|
|
setupChallenges(ctx, client)
|
2018-12-06 23:50:17 +02:00
|
|
|
|
|
|
|
if account.Registration == nil {
|
|
|
|
reg, err := register(ctx, client)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Could not complete registration\n\t%v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
account.Registration = reg
|
|
|
|
if err = accountsStorage.Save(account); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2020-04-27 19:23:17 +02:00
|
|
|
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
|
2018-12-06 23:50:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
certsStorage := NewCertificatesStorage(ctx)
|
|
|
|
certsStorage.CreateRootFolder()
|
|
|
|
|
|
|
|
cert, err := obtainCertificate(ctx, client)
|
|
|
|
if err != nil {
|
|
|
|
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
|
|
|
|
// Due to us not returning partial certificate we can just exit here instead of at the end.
|
|
|
|
log.Fatalf("Could not obtain certificates:\n\t%v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
certsStorage.SaveResource(cert)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
|
|
|
|
// Check for a global accept override
|
|
|
|
if ctx.GlobalBool("accept-tos") {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
log.Printf("Please review the TOS at %s", client.GetToSURL())
|
|
|
|
|
|
|
|
for {
|
|
|
|
fmt.Println("Do you accept the TOS? Y/n")
|
|
|
|
text, err := reader.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Could not read from console: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
text = strings.Trim(text, "\r\n")
|
|
|
|
switch text {
|
|
|
|
case "", "y", "Y":
|
|
|
|
return true
|
|
|
|
case "n", "N":
|
2019-02-16 15:56:15 +02:00
|
|
|
return false
|
2018-12-06 23:50:17 +02:00
|
|
|
default:
|
|
|
|
fmt.Println("Your input was invalid. Please answer with one of Y/y, n/N or by pressing enter.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, error) {
|
|
|
|
accepted := handleTOS(ctx, client)
|
|
|
|
if !accepted {
|
|
|
|
log.Fatal("You did not accept the TOS. Unable to proceed.")
|
|
|
|
}
|
|
|
|
|
|
|
|
if ctx.GlobalBool("eab") {
|
|
|
|
kid := ctx.GlobalString("kid")
|
|
|
|
hmacEncoded := ctx.GlobalString("hmac")
|
|
|
|
|
|
|
|
if kid == "" || hmacEncoded == "" {
|
|
|
|
log.Fatalf("Requires arguments --kid and --hmac.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
|
|
|
TermsOfServiceAgreed: accepted,
|
|
|
|
Kid: kid,
|
|
|
|
HmacEncoded: hmacEncoded,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
|
|
}
|
|
|
|
|
|
|
|
func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Resource, error) {
|
|
|
|
bundle := !ctx.Bool("no-bundle")
|
|
|
|
|
|
|
|
domains := ctx.GlobalStringSlice("domains")
|
|
|
|
if len(domains) > 0 {
|
|
|
|
// obtain a certificate, generating a new private key
|
|
|
|
request := certificate.ObtainRequest{
|
|
|
|
Domains: domains,
|
|
|
|
Bundle: bundle,
|
|
|
|
MustStaple: ctx.Bool("must-staple"),
|
|
|
|
}
|
|
|
|
return client.Certificate.Obtain(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
// read the CSR
|
|
|
|
csr, err := readCSRFile(ctx.GlobalString("csr"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// obtain a certificate for this CSR
|
|
|
|
return client.Certificate.ObtainForCSR(*csr, bundle)
|
|
|
|
}
|