1package cmd 2 3import ( 4 "bufio" 5 "fmt" 6 "os" 7 "strings" 8 9 "github.com/go-acme/lego/v4/certificate" 10 "github.com/go-acme/lego/v4/lego" 11 "github.com/go-acme/lego/v4/log" 12 "github.com/go-acme/lego/v4/registration" 13 "github.com/urfave/cli" 14) 15 16func createRun() cli.Command { 17 return cli.Command{ 18 Name: "run", 19 Usage: "Register an account, then create and install a certificate", 20 Before: func(ctx *cli.Context) error { 21 // we require either domains or csr, but not both 22 hasDomains := len(ctx.GlobalStringSlice("domains")) > 0 23 hasCsr := len(ctx.GlobalString("csr")) > 0 24 if hasDomains && hasCsr { 25 log.Fatal("Please specify either --domains/-d or --csr/-c, but not both") 26 } 27 if !hasDomains && !hasCsr { 28 log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)") 29 } 30 return nil 31 }, 32 Action: run, 33 Flags: []cli.Flag{ 34 cli.BoolFlag{ 35 Name: "no-bundle", 36 Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.", 37 }, 38 cli.BoolFlag{ 39 Name: "must-staple", 40 Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.", 41 }, 42 cli.StringFlag{ 43 Name: "run-hook", 44 Usage: "Define a hook. The hook is executed when the certificates are effectively created.", 45 }, 46 cli.StringFlag{ 47 Name: "preferred-chain", 48 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.", 49 }, 50 cli.StringFlag{ 51 Name: "always-deactivate-authorizations", 52 Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", 53 }, 54 }, 55 } 56} 57 58const rootPathWarningMessage = `!!!! HEADS UP !!!! 59 60Your account credentials have been saved in your Let's Encrypt 61configuration directory at "%s". 62 63You should make a secure backup of this folder now. This 64configuration directory will also contain certificates and 65private keys obtained from Let's Encrypt so making regular 66backups of this folder is ideal. 67` 68 69func run(ctx *cli.Context) error { 70 accountsStorage := NewAccountsStorage(ctx) 71 72 account, client := setup(ctx, accountsStorage) 73 setupChallenges(ctx, client) 74 75 if account.Registration == nil { 76 reg, err := register(ctx, client) 77 if err != nil { 78 log.Fatalf("Could not complete registration\n\t%v", err) 79 } 80 81 account.Registration = reg 82 if err = accountsStorage.Save(account); err != nil { 83 log.Fatal(err) 84 } 85 86 fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath()) 87 } 88 89 certsStorage := NewCertificatesStorage(ctx) 90 certsStorage.CreateRootFolder() 91 92 cert, err := obtainCertificate(ctx, client) 93 if err != nil { 94 // Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error. 95 // Due to us not returning partial certificate we can just exit here instead of at the end. 96 log.Fatalf("Could not obtain certificates:\n\t%v", err) 97 } 98 99 certsStorage.SaveResource(cert) 100 101 meta := map[string]string{ 102 renewEnvAccountEmail: account.Email, 103 renewEnvCertDomain: cert.Domain, 104 renewEnvCertPath: certsStorage.GetFileName(cert.Domain, ".crt"), 105 renewEnvCertKeyPath: certsStorage.GetFileName(cert.Domain, ".key"), 106 } 107 108 return launchHook(ctx.String("run-hook"), meta) 109} 110 111func handleTOS(ctx *cli.Context, client *lego.Client) bool { 112 // Check for a global accept override 113 if ctx.GlobalBool("accept-tos") { 114 return true 115 } 116 117 reader := bufio.NewReader(os.Stdin) 118 log.Printf("Please review the TOS at %s", client.GetToSURL()) 119 120 for { 121 fmt.Println("Do you accept the TOS? Y/n") 122 text, err := reader.ReadString('\n') 123 if err != nil { 124 log.Fatalf("Could not read from console: %v", err) 125 } 126 127 text = strings.Trim(text, "\r\n") 128 switch text { 129 case "", "y", "Y": 130 return true 131 case "n", "N": 132 return false 133 default: 134 fmt.Println("Your input was invalid. Please answer with one of Y/y, n/N or by pressing enter.") 135 } 136 } 137} 138 139func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, error) { 140 accepted := handleTOS(ctx, client) 141 if !accepted { 142 log.Fatal("You did not accept the TOS. Unable to proceed.") 143 } 144 145 if ctx.GlobalBool("eab") { 146 kid := ctx.GlobalString("kid") 147 hmacEncoded := ctx.GlobalString("hmac") 148 149 if kid == "" || hmacEncoded == "" { 150 log.Fatalf("Requires arguments --kid and --hmac.") 151 } 152 153 return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ 154 TermsOfServiceAgreed: accepted, 155 Kid: kid, 156 HmacEncoded: hmacEncoded, 157 }) 158 } 159 160 return client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) 161} 162 163func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Resource, error) { 164 bundle := !ctx.Bool("no-bundle") 165 166 domains := ctx.GlobalStringSlice("domains") 167 if len(domains) > 0 { 168 // obtain a certificate, generating a new private key 169 request := certificate.ObtainRequest{ 170 Domains: domains, 171 Bundle: bundle, 172 MustStaple: ctx.Bool("must-staple"), 173 PreferredChain: ctx.String("preferred-chain"), 174 AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), 175 } 176 return client.Certificate.Obtain(request) 177 } 178 179 // read the CSR 180 csr, err := readCSRFile(ctx.GlobalString("csr")) 181 if err != nil { 182 return nil, err 183 } 184 185 // obtain a certificate for this CSR 186 return client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ 187 CSR: csr, 188 Bundle: bundle, 189 PreferredChain: ctx.String("preferred-chain"), 190 AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), 191 }) 192} 193