From 1818b1ea627b46f5ed0bfec18a148d7ab84eecd6 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 28 Oct 2015 18:12:07 -0600 Subject: [PATCH] letsencrypt: Better error handling, prompt user for SA --- caddy/letsencrypt/letsencrypt.go | 19 ++++++++++++++----- caddy/letsencrypt/renew.go | 3 ++- caddy/letsencrypt/user.go | 23 +++++++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/caddy/letsencrypt/letsencrypt.go b/caddy/letsencrypt/letsencrypt.go index 8037985a..4af30e35 100644 --- a/caddy/letsencrypt/letsencrypt.go +++ b/caddy/letsencrypt/letsencrypt.go @@ -62,19 +62,19 @@ func Activate(configs []server.Config) ([]server.Config, error) { // make client to service this email address with CA server client, err := newClient(leEmail) if err != nil { - return configs, err + return configs, errors.New("error creating client: " + err.Error()) } // client is ready, so let's get free, trusted SSL certificates! yeah! certificates, err := obtainCertificates(client, serverConfigs) if err != nil { - return configs, err + return configs, errors.New("error obtaining cert: " + err.Error()) } // ... that's it. save the certs, keys, and metadata files to disk err = saveCertsAndKeys(certificates) if err != nil { - return configs, err + return configs, errors.New("error saving assets: " + err.Error()) } // it all comes down to this: turning TLS on for all the configs @@ -158,7 +158,10 @@ func newClient(leEmail string) (*acme.Client, error) { } // The client facilitates our communication with the CA server. - client := acme.NewClient(CAUrl, &leUser, rsaKeySizeToUse, exposePort) + client, err := acme.NewClient(CAUrl, &leUser, rsaKeySizeToUse, exposePort) + if err != nil { + return nil, err + } // If not registered, the user must register an account with the CA // and agree to terms @@ -169,7 +172,13 @@ func newClient(leEmail string) (*acme.Client, error) { } leUser.Registration = reg - // TODO: we can just do the agreement once: when registering, right? + if !Agreed && reg.TosURL == "" { + Agreed = promptUserAgreement("", false) // TODO + } + if !Agreed && reg.TosURL == "" { + return nil, errors.New("user must agree to terms") + } + err = client.AgreeToTOS() if err != nil { saveUser(leUser) // TODO: Might as well try, right? Error check? diff --git a/caddy/letsencrypt/renew.go b/caddy/letsencrypt/renew.go index a00eb015..db7345f0 100644 --- a/caddy/letsencrypt/renew.go +++ b/caddy/letsencrypt/renew.go @@ -34,7 +34,8 @@ func keepCertificatesRenewed(configs []server.Config) { // checkCertificateRenewal loops through all configured // sites and looks for certificates to renew. Nothing is mutated // through this function. The changes happen directly on disk. -// It returns the number of certificates renewed and +// It returns the number of certificates renewed and any errors +// that occurred. func processCertificateRenewal(configs []server.Config) (int, []error) { log.Print("[INFO] Processing certificate renewals...") var errs []error diff --git a/caddy/letsencrypt/user.go b/caddy/letsencrypt/user.go index 752cc510..ff4d6acb 100644 --- a/caddy/letsencrypt/user.go +++ b/caddy/letsencrypt/user.go @@ -156,6 +156,29 @@ func getEmail(cfg server.Config) string { return strings.TrimSpace(leEmail) } +// promptUserAgreement prompts the user to agree to the agreement +// at agreementURL via stdin. If the agreement has changed, then pass +// true as the second argument. If this is the user's first time +// agreeing, pass false. It returns whether the user agreed or not. +func promptUserAgreement(agreementURL string, changed bool) bool { + if changed { + fmt.Printf("The Let's Encrypt Subscriber Agreement has changed:\n%s\n", agreementURL) + fmt.Print("Do you agree to the new terms? (y/n): ") + } else { + fmt.Printf("To continue, you must agree to the Let's Encrypt Subscriber Agreement:\n%s\n", agreementURL) + fmt.Print("Do you agree to the terms? (y/n): ") + } + + reader := bufio.NewReader(stdin) // TODO/BUG: This doesn't work when Caddyfile is piped into caddy + answer, err := reader.ReadString('\n') + if err != nil { + return false + } + answer = strings.ToLower(strings.TrimSpace(answer)) + + return answer == "y" || answer == "yes" +} + // stdin is used to read the user's input if prompted; // this is changed by tests during tests. var stdin = io.ReadWriter(os.Stdin)