forked from TrueCloudLab/lego
Move CLI handlers to their own file
Implement Tos accept and start obtain certificates
This commit is contained in:
parent
14627c9d51
commit
7aab5562c1
4 changed files with 212 additions and 53 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
53
cli.go
|
@ -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
96
cli_handlers.go
Normal 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"))
|
||||
}
|
Loading…
Reference in a new issue