Move CLI handlers to their own file

Implement Tos accept and start obtain certificates
This commit is contained in:
xenolf 2015-06-08 23:54:15 +02:00
parent 14627c9d51
commit 7aab5562c1
4 changed files with 212 additions and 53 deletions

View file

@ -5,6 +5,7 @@ import (
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
@ -112,6 +113,92 @@ func (c *Client) Register() (*RegistrationResource, error) {
return reg, nil
}
// AgreeToTos updates the Client registration and sends the agreement to
// the server.
func (c *Client) AgreeToTos() error {
c.user.GetRegistration().Body.Agreement = c.user.GetRegistration().TosURL
jsonBytes, err := json.Marshal(&c.user.GetRegistration().Body)
if err != nil {
return err
}
logger().Printf("Agreement: %s", string(jsonBytes))
resp, err := c.jwsPost(c.user.GetRegistration().URI, jsonBytes)
if err != nil {
return err
}
logResponseBody(resp)
if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("The server returned %d but we expected %d", resp.StatusCode, http.StatusAccepted)
}
logResponseHeaders(resp)
logResponseBody(resp)
return nil
}
// ObtainCertificates tries to obtain certificates from the CA server
// using the challenges it has configured. It also tries to do multiple
// certificate processings at the same time in parallel.
func (c *Client) ObtainCertificates(domains []string) error {
resc, errc := make(chan *authorizationResource), make(chan error)
for _, domain := range domains {
go func(domain string) {
jsonBytes, err := json.Marshal(authorization{Identifier: identifier{Type: "dns", Value: domain}})
if err != nil {
errc <- err
return
}
resp, err := c.jwsPost(c.user.GetRegistration().NewAuthzURL, jsonBytes)
if err != nil {
errc <- err
return
}
if resp.StatusCode != http.StatusCreated {
errc <- fmt.Errorf("Getting challenges for %s failed. Got status %d but expected %d",
domain, resp.StatusCode, http.StatusCreated)
}
links := parseLinks(resp.Header["Link"])
if links["next"] == "" {
logger().Fatalln("The server did not provide enough information to proceed.")
}
var authz authorization
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&authz)
if err != nil {
errc <- err
}
resc <- &authorizationResource{Body: authz, NewCertURL: links["next"], Domain: domain}
}(domain)
}
var responses []*authorizationResource
for i := 0; i < len(domains); i++ {
select {
case res := <-resc:
responses = append(responses, res)
case err := <-errc:
logger().Printf("%v", err)
}
}
close(resc)
close(errc)
return nil
}
func logResponseHeaders(resp *http.Response) {
logger().Println(resp.Status)
for k, v := range resp.Header {

View file

@ -1,5 +1,7 @@
package acme
import "time"
type registrationMessage struct {
Contact []string `json:"contact"`
}
@ -15,6 +17,7 @@ type Registration struct {
} `json:"key"`
Recoverytoken string `json:"recoveryToken"`
Contact []string `json:"contact"`
Agreement string `json:"agreement,omitempty"`
}
// RegistrationResource represents all important informations about a registration
@ -25,3 +28,29 @@ type RegistrationResource struct {
NewAuthzURL string
TosURL string
}
type authorizationResource struct {
Body authorization
Domain string
NewCertURL string
}
type authorization struct {
Identifier identifier `json:"identifier"`
Status string `json:"status,omitempty"`
Expires time.Time `json:"expires,omitempty"`
Challenges []challenge `json:"challenges,omitempty"`
Combinations [][]int `json:"combinations,omitempty"`
}
type identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
type challenge struct {
Type string `json:"type"`
Status string `json:"status"`
URI string `json:"uri"`
Token string `json:"token"`
}

53
cli.go
View file

@ -5,7 +5,6 @@ import (
"os"
"github.com/codegangsta/cli"
"github.com/xenolf/lego/acme"
)
// Logger is used to log errors; if nil, the default log.Logger is used.
@ -138,55 +137,3 @@ func main() {
app.Run(os.Args)
}
func checkFolder(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, 0700)
}
return nil
}
func run(c *cli.Context) {
err := checkFolder(c.GlobalString("config-dir"))
if err != nil {
logger().Fatalf("Cound not check/create path: %v", err)
}
conf := NewConfiguration(c)
//TODO: move to account struct? Currently MUST pass email.
if !c.GlobalIsSet("email") {
logger().Fatal("You have to pass an account (email address) to the program using --email or -m")
}
acc := NewAccount(c.GlobalString("email"), conf)
client := acme.NewClient(c.GlobalString("server"), acc)
if acc.Registration == nil {
reg, err := client.Register()
if err != nil {
logger().Fatalf("Could not complete registration -> %v", err)
}
acc.Registration = reg
acc.Save()
logger().Print("!!!! HEADS UP !!!!")
logger().Printf(`
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.
If you lose your account credentials, you can recover
them using the token
"%s".
You must write that down and put it in a safe place.`, c.GlobalString("config-dir"), reg.Body.Recoverytoken)
}
if !c.GlobalIsSet("domains") {
logger().Fatal("Please specify --domains")
}
}

96
cli_handlers.go Normal file
View file

@ -0,0 +1,96 @@
package main
import (
"bufio"
"os"
"strings"
"github.com/codegangsta/cli"
"github.com/xenolf/lego/acme"
)
func checkFolder(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, 0700)
}
return nil
}
func run(c *cli.Context) {
err := checkFolder(c.GlobalString("config-dir"))
if err != nil {
logger().Fatalf("Cound not check/create path: %v", err)
}
conf := NewConfiguration(c)
//TODO: move to account struct? Currently MUST pass email.
if !c.GlobalIsSet("email") {
logger().Fatal("You have to pass an account (email address) to the program using --email or -m")
}
acc := NewAccount(c.GlobalString("email"), conf)
client := acme.NewClient(c.GlobalString("server"), acc)
if acc.Registration == nil {
reg, err := client.Register()
if err != nil {
logger().Fatalf("Could not complete registration -> %v", err)
}
acc.Registration = reg
acc.Save()
logger().Print("!!!! HEADS UP !!!!")
logger().Printf(`
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.
If you lose your account credentials, you can recover
them using the token
"%s".
You must write that down and put it in a safe place.`, c.GlobalString("config-dir"), reg.Body.Recoverytoken)
}
if acc.Registration.Body.Agreement == "" {
if !c.GlobalBool("agree-tos") {
reader := bufio.NewReader(os.Stdin)
logger().Printf("Please review the TOS at %s", acc.Registration.TosURL)
for {
logger().Println("Do you accept the TOS? Y/n")
text, err := reader.ReadString('\n')
if err != nil {
logger().Fatalf("Could not read from console -> %v", err)
}
text = strings.Trim(text, "\r\n")
if text == "n" {
logger().Fatal("You did not accept the TOS. Unable to proceed.")
}
if text == "Y" || text == "y" || text == "" {
err = client.AgreeToTos()
if err != nil {
logger().Fatalf("Could not agree to tos -> %v", err)
}
acc.Save()
break
}
logger().Println("Your input was invalid. Please answer with one of Y/y, n or by pressing enter.")
}
}
}
if !c.GlobalIsSet("domains") {
logger().Fatal("Please specify --domains")
}
client.ObtainCertificates(c.GlobalStringSlice("domains"))
}