forked from TrueCloudLab/lego
42941ccea6
- Packages - Isolate code used by the CLI into the package `cmd` - (experimental) Add e2e tests for HTTP01, TLS-ALPN-01 and DNS-01, use [Pebble](https://github.com/letsencrypt/pebble) and [challtestsrv](https://github.com/letsencrypt/boulder/tree/master/test/challtestsrv) - Support non-ascii domain name (punnycode) - Check all challenges in a predictable order - No more global exported variables - Archive revoked certificates - Fixes revocation for subdomains and non-ascii domains - Disable pending authorizations - use pointer for RemoteError/ProblemDetails - Poll authz URL instead of challenge URL - The ability for a DNS provider to solve the challenge sequentially - Check all nameservers in a predictable order - Option to disable the complete propagation Requirement - CLI, support for renew with CSR - CLI, add SAN on renew - Add command to list certificates. - Logs every iteration of waiting for the propagation - update DNSimple client - update github.com/miekg/dns
304 lines
5.9 KiB
Go
304 lines
5.9 KiB
Go
package loader
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/xenolf/lego/platform/wait"
|
|
)
|
|
|
|
const (
|
|
cmdNamePebble = "pebble"
|
|
cmdNameChallSrv = "challtestsrv"
|
|
)
|
|
|
|
type CmdOption struct {
|
|
HealthCheckURL string
|
|
Args []string
|
|
Env []string
|
|
Dir string
|
|
}
|
|
|
|
type EnvLoader struct {
|
|
PebbleOptions *CmdOption
|
|
LegoOptions []string
|
|
ChallSrv *CmdOption
|
|
lego string
|
|
}
|
|
|
|
func (l *EnvLoader) MainTest(m *testing.M) int {
|
|
_, force := os.LookupEnv("LEGO_E2E_TESTS")
|
|
if _, ci := os.LookupEnv("CI"); !ci && !force {
|
|
fmt.Fprintln(os.Stderr, "skipping test: e2e tests are disabled. (no 'CI' or 'LEGO_E2E_TESTS' env var)")
|
|
fmt.Println("PASS")
|
|
return 0
|
|
}
|
|
|
|
if _, err := exec.LookPath("git"); err != nil {
|
|
fmt.Fprintln(os.Stderr, "skipping because git command not found")
|
|
fmt.Println("PASS")
|
|
return 0
|
|
}
|
|
|
|
if l.PebbleOptions != nil {
|
|
if _, err := exec.LookPath(cmdNamePebble); err != nil {
|
|
fmt.Fprintln(os.Stderr, "skipping because pebble binary not found")
|
|
fmt.Println("PASS")
|
|
return 0
|
|
}
|
|
}
|
|
|
|
if l.ChallSrv != nil {
|
|
if _, err := exec.LookPath(cmdNameChallSrv); err != nil {
|
|
fmt.Fprintln(os.Stderr, "skipping because challtestsrv binary not found")
|
|
fmt.Println("PASS")
|
|
return 0
|
|
}
|
|
}
|
|
|
|
pebbleTearDown := l.launchPebble()
|
|
defer pebbleTearDown()
|
|
|
|
challSrvTearDown := l.launchChallSrv()
|
|
defer challSrvTearDown()
|
|
|
|
legoBinary, tearDown, err := buildLego()
|
|
defer tearDown()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
return 1
|
|
}
|
|
|
|
l.lego = legoBinary
|
|
|
|
if l.PebbleOptions != nil && l.PebbleOptions.HealthCheckURL != "" {
|
|
pebbleHealthCheck(l.PebbleOptions)
|
|
}
|
|
|
|
return m.Run()
|
|
}
|
|
|
|
func (l *EnvLoader) RunLego(arg ...string) ([]byte, error) {
|
|
cmd := exec.Command(l.lego, arg...)
|
|
cmd.Env = l.LegoOptions
|
|
|
|
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
|
|
|
|
return cmd.CombinedOutput()
|
|
}
|
|
|
|
func (l *EnvLoader) launchPebble() func() {
|
|
if l.PebbleOptions == nil {
|
|
return func() {}
|
|
}
|
|
|
|
pebble, outPebble := l.cmdPebble()
|
|
go func() {
|
|
err := pebble.Run()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}()
|
|
|
|
return func() {
|
|
err := pebble.Process.Kill()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
fmt.Println(outPebble.String())
|
|
}
|
|
}
|
|
|
|
func (l *EnvLoader) cmdPebble() (*exec.Cmd, *bytes.Buffer) {
|
|
cmd := exec.Command(cmdNamePebble, l.PebbleOptions.Args...)
|
|
cmd.Env = l.PebbleOptions.Env
|
|
|
|
dir, err := filepath.Abs(l.PebbleOptions.Dir)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
cmd.Dir = dir
|
|
|
|
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
|
|
|
|
var b bytes.Buffer
|
|
cmd.Stdout = &b
|
|
cmd.Stderr = &b
|
|
|
|
return cmd, &b
|
|
}
|
|
|
|
func pebbleHealthCheck(options *CmdOption) {
|
|
client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
|
|
err := wait.For(10*time.Second, 500*time.Millisecond, func() (bool, error) {
|
|
resp, err := client.Get(options.HealthCheckURL)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (l *EnvLoader) launchChallSrv() func() {
|
|
if l.ChallSrv == nil {
|
|
return func() {}
|
|
}
|
|
|
|
challtestsrv, outChalSrv := l.cmdChallSrv()
|
|
go func() {
|
|
err := challtestsrv.Run()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}()
|
|
|
|
return func() {
|
|
err := challtestsrv.Process.Kill()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
fmt.Println(outChalSrv.String())
|
|
}
|
|
}
|
|
|
|
func (l *EnvLoader) cmdChallSrv() (*exec.Cmd, *bytes.Buffer) {
|
|
cmd := exec.Command(cmdNameChallSrv, l.ChallSrv.Args...)
|
|
|
|
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
|
|
|
|
var b bytes.Buffer
|
|
cmd.Stdout = &b
|
|
cmd.Stderr = &b
|
|
|
|
return cmd, &b
|
|
}
|
|
|
|
func buildLego() (string, func(), error) {
|
|
here, err := os.Getwd()
|
|
if err != nil {
|
|
return "", func() {}, err
|
|
}
|
|
defer func() { _ = os.Chdir(here) }()
|
|
|
|
buildPath, err := ioutil.TempDir("", "lego_test")
|
|
if err != nil {
|
|
return "", func() {}, err
|
|
}
|
|
|
|
projectRoot, err := getProjectRoot()
|
|
if err != nil {
|
|
return "", func() {}, err
|
|
}
|
|
|
|
mainFolder := filepath.Join(projectRoot, "cmd", "lego")
|
|
|
|
err = os.Chdir(mainFolder)
|
|
if err != nil {
|
|
return "", func() {}, err
|
|
}
|
|
|
|
binary := filepath.Join(buildPath, "lego")
|
|
|
|
err = build(binary)
|
|
if err != nil {
|
|
return "", func() {}, err
|
|
}
|
|
|
|
err = os.Chdir(here)
|
|
if err != nil {
|
|
return "", func() {}, err
|
|
}
|
|
|
|
return binary, func() {
|
|
_ = os.RemoveAll(buildPath)
|
|
CleanLegoFiles()
|
|
}, nil
|
|
}
|
|
|
|
func getProjectRoot() (string, error) {
|
|
git := exec.Command("git", "rev-parse", "--show-toplevel")
|
|
|
|
output, err := git.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s\n", output)
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
func build(binary string) error {
|
|
toolPath, err := goToolPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd := exec.Command(toolPath, "build", "-o", binary)
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s\n", output)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func goToolPath() (string, error) {
|
|
// inspired by go1.11.1/src/internal/testenv/testenv.go
|
|
if os.Getenv("GO_GCFLAGS") != "" {
|
|
return "", errors.New("'go build' not compatible with setting $GO_GCFLAGS")
|
|
}
|
|
|
|
if runtime.GOOS == "darwin" && strings.HasPrefix(runtime.GOARCH, "arm") {
|
|
return "", fmt.Errorf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
}
|
|
|
|
return goTool()
|
|
}
|
|
|
|
func goTool() (string, error) {
|
|
var exeSuffix string
|
|
if runtime.GOOS == "windows" {
|
|
exeSuffix = ".exe"
|
|
}
|
|
|
|
path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
|
|
if _, err := os.Stat(path); err == nil {
|
|
return path, nil
|
|
}
|
|
|
|
goBin, err := exec.LookPath("go" + exeSuffix)
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot find go tool: %v", err)
|
|
}
|
|
|
|
return goBin, nil
|
|
}
|
|
|
|
func CleanLegoFiles() {
|
|
cmd := exec.Command("rm", "-rf", ".lego")
|
|
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Println(string(output))
|
|
}
|
|
}
|