mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 02:03:20 -04:00 
			
		
		
		
	
		
			
	
	
		
			201 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			201 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package https
 | ||
|  | 
 | ||
|  | import (
 | ||
|  | 	"bufio"
 | ||
|  | 	"crypto"
 | ||
|  | 	"crypto/ecdsa"
 | ||
|  | 	"crypto/elliptic"
 | ||
|  | 	"crypto/rand"
 | ||
|  | 	"encoding/json"
 | ||
|  | 	"errors"
 | ||
|  | 	"fmt"
 | ||
|  | 	"io"
 | ||
|  | 	"io/ioutil"
 | ||
|  | 	"os"
 | ||
|  | 	"strings"
 | ||
|  | 
 | ||
|  | 	"github.com/miekg/coredns/server"
 | ||
|  | 	"github.com/xenolf/lego/acme"
 | ||
|  | )
 | ||
|  | 
 | ||
|  | // User represents a Let's Encrypt user account.
 | ||
|  | type User struct {
 | ||
|  | 	Email        string
 | ||
|  | 	Registration *acme.RegistrationResource
 | ||
|  | 	key          crypto.PrivateKey
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // GetEmail gets u's email.
 | ||
|  | func (u User) GetEmail() string {
 | ||
|  | 	return u.Email
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // GetRegistration gets u's registration resource.
 | ||
|  | func (u User) GetRegistration() *acme.RegistrationResource {
 | ||
|  | 	return u.Registration
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // GetPrivateKey gets u's private key.
 | ||
|  | func (u User) GetPrivateKey() crypto.PrivateKey {
 | ||
|  | 	return u.key
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // getUser loads the user with the given email from disk.
 | ||
|  | // If the user does not exist, it will create a new one,
 | ||
|  | // but it does NOT save new users to the disk or register
 | ||
|  | // them via ACME. It does NOT prompt the user.
 | ||
|  | func getUser(email string) (User, error) {
 | ||
|  | 	var user User
 | ||
|  | 
 | ||
|  | 	// open user file
 | ||
|  | 	regFile, err := os.Open(storage.UserRegFile(email))
 | ||
|  | 	if err != nil {
 | ||
|  | 		if os.IsNotExist(err) {
 | ||
|  | 			// create a new user
 | ||
|  | 			return newUser(email)
 | ||
|  | 		}
 | ||
|  | 		return user, err
 | ||
|  | 	}
 | ||
|  | 	defer regFile.Close()
 | ||
|  | 
 | ||
|  | 	// load user information
 | ||
|  | 	err = json.NewDecoder(regFile).Decode(&user)
 | ||
|  | 	if err != nil {
 | ||
|  | 		return user, err
 | ||
|  | 	}
 | ||
|  | 
 | ||
|  | 	// load their private key
 | ||
|  | 	user.key, err = loadPrivateKey(storage.UserKeyFile(email))
 | ||
|  | 	if err != nil {
 | ||
|  | 		return user, err
 | ||
|  | 	}
 | ||
|  | 
 | ||
|  | 	return user, nil
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // saveUser persists a user's key and account registration
 | ||
|  | // to the file system. It does NOT register the user via ACME
 | ||
|  | // or prompt the user.
 | ||
|  | func saveUser(user User) error {
 | ||
|  | 	// make user account folder
 | ||
|  | 	err := os.MkdirAll(storage.User(user.Email), 0700)
 | ||
|  | 	if err != nil {
 | ||
|  | 		return err
 | ||
|  | 	}
 | ||
|  | 
 | ||
|  | 	// save private key file
 | ||
|  | 	err = savePrivateKey(user.key, storage.UserKeyFile(user.Email))
 | ||
|  | 	if err != nil {
 | ||
|  | 		return err
 | ||
|  | 	}
 | ||
|  | 
 | ||
|  | 	// save registration file
 | ||
|  | 	jsonBytes, err := json.MarshalIndent(&user, "", "\t")
 | ||
|  | 	if err != nil {
 | ||
|  | 		return err
 | ||
|  | 	}
 | ||
|  | 
 | ||
|  | 	return ioutil.WriteFile(storage.UserRegFile(user.Email), jsonBytes, 0600)
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // newUser creates a new User for the given email address
 | ||
|  | // with a new private key. This function does NOT save the
 | ||
|  | // user to disk or register it via ACME. If you want to use
 | ||
|  | // a user account that might already exist, call getUser
 | ||
|  | // instead. It does NOT prompt the user.
 | ||
|  | func newUser(email string) (User, error) {
 | ||
|  | 	user := User{Email: email}
 | ||
|  | 	privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
 | ||
|  | 	if err != nil {
 | ||
|  | 		return user, errors.New("error generating private key: " + err.Error())
 | ||
|  | 	}
 | ||
|  | 	user.key = privateKey
 | ||
|  | 	return user, nil
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // getEmail does everything it can to obtain an email
 | ||
|  | // address from the user to use for TLS for cfg. If it
 | ||
|  | // cannot get an email address, it returns empty string.
 | ||
|  | // (It will warn the user of the consequences of an
 | ||
|  | // empty email.) This function MAY prompt the user for
 | ||
|  | // input. If userPresent is false, the operator will
 | ||
|  | // NOT be prompted and an empty email may be returned.
 | ||
|  | func getEmail(cfg server.Config, userPresent bool) string {
 | ||
|  | 	// First try the tls directive from the Caddyfile
 | ||
|  | 	leEmail := cfg.TLS.LetsEncryptEmail
 | ||
|  | 	if leEmail == "" {
 | ||
|  | 		// Then try memory (command line flag or typed by user previously)
 | ||
|  | 		leEmail = DefaultEmail
 | ||
|  | 	}
 | ||
|  | 	if leEmail == "" {
 | ||
|  | 		// Then try to get most recent user email ~/.caddy/users file
 | ||
|  | 		userDirs, err := ioutil.ReadDir(storage.Users())
 | ||
|  | 		if err == nil {
 | ||
|  | 			var mostRecent os.FileInfo
 | ||
|  | 			for _, dir := range userDirs {
 | ||
|  | 				if !dir.IsDir() {
 | ||
|  | 					continue
 | ||
|  | 				}
 | ||
|  | 				if mostRecent == nil || dir.ModTime().After(mostRecent.ModTime()) {
 | ||
|  | 					leEmail = dir.Name()
 | ||
|  | 					DefaultEmail = leEmail // save for next time
 | ||
|  | 				}
 | ||
|  | 			}
 | ||
|  | 		}
 | ||
|  | 	}
 | ||
|  | 	if leEmail == "" && userPresent {
 | ||
|  | 		// Alas, we must bother the user and ask for an email address;
 | ||
|  | 		// if they proceed they also agree to the SA.
 | ||
|  | 		reader := bufio.NewReader(stdin)
 | ||
|  | 		fmt.Println("\nYour sites will be served over HTTPS automatically using Let's Encrypt.")
 | ||
|  | 		fmt.Println("By continuing, you agree to the Let's Encrypt Subscriber Agreement at:")
 | ||
|  | 		fmt.Println("  " + saURL) // TODO: Show current SA link
 | ||
|  | 		fmt.Println("Please enter your email address so you can recover your account if needed.")
 | ||
|  | 		fmt.Println("You can leave it blank, but you'll lose the ability to recover your account.")
 | ||
|  | 		fmt.Print("Email address: ")
 | ||
|  | 		var err error
 | ||
|  | 		leEmail, err = reader.ReadString('\n')
 | ||
|  | 		if err != nil {
 | ||
|  | 			return ""
 | ||
|  | 		}
 | ||
|  | 		leEmail = strings.TrimSpace(leEmail)
 | ||
|  | 		DefaultEmail = leEmail
 | ||
|  | 		Agreed = true
 | ||
|  | 	}
 | ||
|  | 	return 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)
 | ||
|  | 	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)
 | ||
|  | 
 | ||
|  | // The name of the folder for accounts where the email
 | ||
|  | // address was not provided; default 'username' if you will.
 | ||
|  | const emptyEmail = "default"
 | ||
|  | 
 | ||
|  | // TODO: Use latest
 | ||
|  | const saURL = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
 |