Make solvers configurable.

Allows selecting which solvers are available, and specifying options for them.
This commit is contained in:
Tommie Gannert 2015-12-05 21:01:08 +00:00
parent 039b7c50dc
commit b2c88d7a5d
5 changed files with 68 additions and 15 deletions

View file

@ -16,8 +16,13 @@ import (
"time"
)
// Logger is an optional custom logger.
var Logger *log.Logger
var (
// DefaultSolvers is the set of solvers to use if none is given to NewClient.
DefaultSolvers = []string{"http-01", "tls-sni-01"}
// Logger is an optional custom logger.
Logger *log.Logger
)
// logf writes a log entry. It uses Logger if not
// nil, otherwise it uses the default log.Logger.
@ -56,9 +61,12 @@ type Client struct {
// the ACME directory located at caDirURL for the rest of its actions. It will
// generate private keys for certificates of size keyBits. And, if the challenge
// type requires it, the client will open a port at optPort to solve the challenge.
// If optPort is blank, the port required by the spec will be used, but you must
// forward the required port to optPort for the challenge to succeed.
func NewClient(caDirURL string, user User, keyBits int, optPort string) (*Client, error) {
//
// If optSolvers is nil, the value of DefaultSolvers is used. If given explicitly,
// it is a set of solver names to enable. The "http-01" and "tls-sni-01" solvers
// take an optional TCP port to listen on after a colon, e.g. "http-01:80". If
// the port is not specified, the port required by the spec will be used.
func NewClient(caDirURL string, user User, keyBits int, optSolvers []string) (*Client, error) {
privKey := user.GetPrivateKey()
if privKey == nil {
return nil, errors.New("private key was nil")
@ -92,8 +100,30 @@ func NewClient(caDirURL string, user User, keyBits int, optPort string) (*Client
// Add all available solvers with the right index as per ACME
// spec to this map. Otherwise they won`t be found.
solvers := make(map[string]solver)
solvers["http-01"] = &httpChallenge{jws: jws, validate: validate, optPort: optPort}
solvers["tls-sni-01"] = &tlsSNIChallenge{jws: jws, validate: validate, optPort: optPort}
if optSolvers == nil {
optSolvers = DefaultSolvers
}
for _, s := range optSolvers {
ss := strings.SplitN(s, ":", 2)
switch ss[0] {
case "http-01":
optPort := ""
if len(ss) > 1 {
optPort = ss[1]
}
solvers["http-01"] = &httpChallenge{jws: jws, validate: validate, optPort: optPort}
case "tls-sni-01":
optPort := ""
if len(ss) > 1 {
optPort = ss[1]
}
solvers["tls-sni-01"] = &tlsSNIChallenge{jws: jws, validate: validate, optPort: optPort}
default:
return nil, fmt.Errorf("unknown solver: %s", s)
}
}
return &Client{directory: dir, user: user, jws: jws, keyBits: keyBits, solvers: solvers}, nil
}

View file

@ -27,8 +27,7 @@ func TestNewClient(t *testing.T) {
w.Write(data)
}))
caURL, optPort := ts.URL, "1234"
client, err := NewClient(caURL, user, keyBits, optPort)
client, err := NewClient(ts.URL, user, keyBits, nil)
if err != nil {
t.Fatalf("Could not create client: %v", err)
}
@ -47,6 +46,30 @@ func TestNewClient(t *testing.T) {
if expected, actual := 2, len(client.solvers); actual != expected {
t.Fatalf("Expected %d solver(s), got %d", expected, actual)
}
}
func TestNewClientOptPort(t *testing.T) {
keyBits := 32 // small value keeps test fast
key, err := rsa.GenerateKey(rand.Reader, keyBits)
if err != nil {
t.Fatal("Could not generate test key:", err)
}
user := mockUser{
email: "test@test.com",
regres: new(RegistrationResource),
privatekey: key,
}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
data, _ := json.Marshal(directory{NewAuthzURL: "http://test", NewCertURL: "http://test", NewRegURL: "http://test", RevokeCertURL: "http://test"})
w.Write(data)
}))
optPort := "1234"
client, err := NewClient(ts.URL, user, keyBits, []string{"http-01:" + optPort})
if err != nil {
t.Fatalf("Could not create client: %v", err)
}
httpSolver, ok := client.solvers["http-01"].(*httpChallenge)
if !ok {

6
cli.go
View file

@ -74,9 +74,9 @@ func main() {
Usage: "Directory to use for storing the data",
Value: defaultPath,
},
cli.StringFlag{
Name: "port",
Usage: "Challenges will use this port to listen on. Please make sure to forward port 443 to this port on your machine. Otherwise use setcap on the binary",
cli.StringSliceFlag{
Name: "solvers, S",
Usage: "Add an explicit solver for challenges. Solvers: \"http-01[:port]\", \"tls-sni-01[:port]\".",
},
}

View file

@ -33,7 +33,7 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
//TODO: move to account struct? Currently MUST pass email.
acc := NewAccount(c.GlobalString("email"), conf)
client, err := acme.NewClient(c.GlobalString("server"), acc, conf.RsaBits(), conf.OptPort())
client, err := acme.NewClient(c.GlobalString("server"), acc, conf.RsaBits(), conf.Solvers())
if err != nil {
logger().Fatal("Could not create client:", err)
}

View file

@ -24,8 +24,8 @@ func (c *Configuration) RsaBits() int {
return c.context.GlobalInt("rsa-key-size")
}
func (c *Configuration) OptPort() string {
return c.context.GlobalString("port")
func (c *Configuration) Solvers() []string {
return c.context.GlobalStringSlice("solvers")
}
// ServerPath returns the OS dependent path to the data for a specific CA