New challenges management. (#741)

This commit is contained in:
Ludovic Fernandez 2019-01-03 16:59:53 +01:00 committed by GitHub
parent 9979087572
commit 43401f2475
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 190 additions and 270 deletions

View file

@ -38,7 +38,6 @@
exclude = [ exclude = [
"Error return value of (.+) is not checked", "Error return value of (.+) is not checked",
"exported (type|method|function) (.+) should have comment or be unexported", "exported (type|method|function) (.+) should have comment or be unexported",
"possible misuse of unsafe.Pointer",
"cyclomatic complexity (.+) of func `NewDNSChallengeProviderByName` is high (.+)", # providers/dns/dns_providers.go "cyclomatic complexity (.+) of func `NewDNSChallengeProviderByName` is high (.+)", # providers/dns/dns_providers.go
"string `(lego\\.wtf|manhattan)` has (\\d+) occurrences, make it a constant", #providers/dns/gcloud/googlecloud_test.go "string `(lego\\.wtf|manhattan)` has (\\d+) occurrences, make it a constant", #providers/dns/gcloud/googlecloud_test.go

View file

@ -12,8 +12,8 @@ Let's Encrypt client and ACME library written in Go
### Binaries ### Binaries
To get the binary just download the latest release for your OS/Arch from [the release page](https://github.com/xenolf/lego/releases) To get the binary just download the latest release for your OS/Arch from [the release page](https://github.com/xenolf/lego/releases) and put the binary somewhere convenient.
and put the binary somewhere convenient. lego does not assume anything about the location you run it from. lego does not assume anything about the location you run it from.
### From Docker ### From Docker
@ -55,7 +55,7 @@ go get -u github.com/xenolf/lego/cmd/lego
Please keep in mind that CLI switches and APIs are still subject to change. Please keep in mind that CLI switches and APIs are still subject to change.
When using the standard `--path` option, all certificates and account configurations are saved to a folder *.lego* in the current working directory. When using the standard `--path` option, all certificates and account configurations are saved to a folder `.lego` in the current working directory.
## Usage ## Usage
@ -75,30 +75,31 @@ COMMANDS:
help, h Shows a list of commands or help for one command help, h Shows a list of commands or help for one command
GLOBAL OPTIONS: GLOBAL OPTIONS:
--domains value, -d value Add a domain to the process. Can be specified multiple times. --domains value, -d value Add a domain to the process. Can be specified multiple times.
--server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory")
--accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.
--email value, -m value Email used for registration and recovery contact. --email value, -m value Email used for registration and recovery contact.
--csr value, -c value Certificate signing request filename, if an external CSR is to be used --csr value, -c value Certificate signing request filename, if an external CSR is to be used.
--eab Use External Account Binding for account registration. Requires --kid and --hmac. --eab Use External Account Binding for account registration. Requires --kid and --hmac.
--kid value Key identifier from External CA. Used for External Account Binding. --kid value Key identifier from External CA. Used for External Account Binding.
--hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding. --hmac value MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.
--key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384 (default: "rsa2048") --key-type value, -k value Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384. (default: "rsa2048")
--filename value Filename of the generated certificate --filename value (deprecated) Filename of the generated certificate.
--path value Directory to use for storing the data (default: "./.lego") --path value Directory to use for storing the data. (default: "./.lego")
--exclude value, -x value Explicitly disallow solvers by name from being used. Solvers: "http-01", "dns-01", "tls-alpn-01". --http Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges.
--http-timeout value Set the HTTP timeout value to a specific value in seconds. The default is 10 seconds. (default: 0) --http.port value Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port. (default: ":80")
--webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge --http.webroot value Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge.
--memcached-host value Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts. --http.memcached-host value Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.
--http value Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port --tls Use the TLS challenge to solve challenges. Can be mixed with other types of challenges.
--tls value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port --tls.port value Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port. (default: ":443")
--dns value Solve a DNS challenge using the specified provider. Disables all other challenges. Run 'lego dnshelp' for help on usage. --dns value Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.
--dns-disable-cp By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers. --dns.disable-cp By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.
--dns-resolvers value Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined. --dns.resolvers value Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. The default is 10 seconds. (default: 0) --http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
--pem Generate a .pem file by concatenating the .key and .crt files together. --dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. (default: 10)
--help, -h show help --pem Generate a .pem file by concatenating the .key and .crt files together.
--version, -v print the version --help, -h show help
--version, -v print the version
``` ```
### Sudo ### Sudo
@ -107,14 +108,14 @@ The CLI does not require root permissions but needs to bind to port 80 and 443 f
To run the CLI without sudo, you have four options: To run the CLI without sudo, you have four options:
- Use setcap 'cap_net_bind_service=+ep' /path/to/program - Use setcap 'cap_net_bind_service=+ep' /path/to/program
- Pass the `--http` or/and the `--tls` option and specify a custom port to bind to. In this case you have to forward port 80/443 to these custom ports (see [Port Usage](#port-usage)). - Pass the `--http.port` or/and the `--tls.port` option and specify a custom port to bind to. In this case you have to forward port 80/443 to these custom ports (see [Port Usage](#port-usage)).
- Pass the `--webroot` option and specify the path to your webroot folder. In this case the challenge will be written in a file in `.well-known/acme-challenge/` inside your webroot. - Pass the `--http.webroot` option and specify the path to your webroot folder. In this case the challenge will be written in a file in `.well-known/acme-challenge/` inside your webroot.
- Pass the `--dns` option and specify a DNS provider. - Pass the `--dns` option and specify a DNS provider.
### Port Usage ### Port Usage
By default lego assumes it is able to bind to ports 80 and 443 to solve challenges. By default lego assumes it is able to bind to ports 80 and 443 to solve challenges.
If this is not possible in your environment, you can use the `--http` and `--tls` options to instruct If this is not possible in your environment, you can use the `--http.port` and `--tls.port` options to instruct
lego to listen on that interface:port for any incoming challenges. lego to listen on that interface:port for any incoming challenges.
If you are using this option, make sure you proxy all of the following traffic to these ports. If you are using this option, make sure you proxy all of the following traffic to these ports.
@ -131,13 +132,14 @@ This traffic redirection is only needed as long as lego solves challenges. As so
### CLI Example ### CLI Example
Assumes the `lego` binary has permission to bind to ports 80 and 443. You can get a pre-built binary from the [releases](https://github.com/xenolf/lego/releases) page. Assumes the `lego` binary has permission to bind to ports 80 and 443.
You can get a pre-built binary from the [releases](https://github.com/xenolf/lego/releases) page.
If your environment does not allow you to bind to these ports, please read [Port Usage](#port-usage). If your environment does not allow you to bind to these ports, please read [Port Usage](#port-usage).
Obtain a certificate: Obtain a certificate:
```bash ```bash
lego --email="foo@bar.com" --domains="example.com" run lego --email="foo@bar.com" --domains="example.com" --http run
``` ```
(Find your certificate in the `.lego` folder of current working directory.) (Find your certificate in the `.lego` folder of current working directory.)
@ -145,13 +147,13 @@ lego --email="foo@bar.com" --domains="example.com" run
To renew the certificate: To renew the certificate:
```bash ```bash
lego --email="foo@bar.com" --domains="example.com" renew lego --email="foo@bar.com" --domains="example.com" --http renew
``` ```
To renew the certificate only if it expires within 30 days To renew the certificate only if it expires within 30 days
```bash ```bash
lego --email="foo@bar.com" --domains="example.com" renew --days 30 lego --email="foo@bar.com" --domains="example.com" --http renew --days 30
``` ```
Obtain a certificate using the DNS challenge and AWS Route 53: Obtain a certificate using the DNS challenge and AWS Route 53:
@ -160,17 +162,16 @@ Obtain a certificate using the DNS challenge and AWS Route 53:
AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=my_id AWS_SECRET_ACCESS_KEY=my_key lego --email="foo@bar.com" --domains="example.com" --dns="route53" run AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=my_id AWS_SECRET_ACCESS_KEY=my_key lego --email="foo@bar.com" --domains="example.com" --dns="route53" run
``` ```
Note that `--dns=foo` implies `--exclude=http-01`. lego will not attempt other challenges if you've told it to use DNS instead.
Obtain a certificate given a certificate signing request (CSR) generated by something else: Obtain a certificate given a certificate signing request (CSR) generated by something else:
```bash ```bash
lego --email="foo@bar.com" --csr=/path/to/csr.pem run lego --email="foo@bar.com" --http --csr=/path/to/csr.pem run
``` ```
(lego will infer the domains to be validated based on the contents of the CSR, so make sure the CSR's Common Name and optional SubjectAltNames are set correctly.) (lego will infer the domains to be validated based on the contents of the CSR, so make sure the CSR's Common Name and optional SubjectAltNames are set correctly.)
lego defaults to communicating with the production Let's Encrypt ACME server. If you'd like to test something without issuing real certificates, consider using the staging endpoint instead: lego defaults to communicating with the production Let's Encrypt ACME server.
If you'd like to test something without issuing real certificates, consider using the staging endpoint instead:
```bash ```bash
lego --server=https://acme-staging-v02.api.letsencrypt.org/directory … lego --server=https://acme-staging-v02.api.letsencrypt.org/directory …
@ -193,6 +194,8 @@ import (
"github.com/xenolf/lego/certcrypto" "github.com/xenolf/lego/certcrypto"
"github.com/xenolf/lego/certificate" "github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge/http01"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego" "github.com/xenolf/lego/lego"
"github.com/xenolf/lego/registration" "github.com/xenolf/lego/registration"
) )
@ -243,10 +246,12 @@ func main() {
// because we aren't running as root and can't bind a listener to port 80 and 443 // because we aren't running as root and can't bind a listener to port 80 and 443
// (used later when we attempt to pass challenges). Keep in mind that you still // (used later when we attempt to pass challenges). Keep in mind that you still
// need to proxy challenge traffic to port 5002 and 5001. // need to proxy challenge traffic to port 5002 and 5001.
if err = client.Challenge.SetHTTP01Address(":5002"); err != nil { err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002"))
if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err = client.Challenge.SetTLSALPN01Address(":5001"); err != nil { err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5001"))
if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View file

@ -140,6 +140,8 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
// CleanUp cleans the challenge. // CleanUp cleans the challenge.
func (c *Challenge) CleanUp(authz acme.Authorization) error { func (c *Challenge) CleanUp(authz acme.Authorization) error {
log.Infof("[%s] acme: Cleaning DNS-01 challenge", challenge.GetTargetedDomain(authz))
chlng, err := challenge.FindChallenge(challenge.DNS01, authz) chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
if err != nil { if err != nil {
return err return err

View file

@ -3,7 +3,6 @@ package resolver
import ( import (
"errors" "errors"
"fmt" "fmt"
"net"
"sort" "sort"
"strconv" "strconv"
"time" "time"
@ -21,7 +20,7 @@ type byType []acme.Challenge
func (a byType) Len() int { return len(a) } func (a byType) Len() int { return len(a) }
func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byType) Less(i, j int) bool { return a[i].Type < a[j].Type } func (a byType) Less(i, j int) bool { return a[i].Type > a[j].Type }
type SolverManager struct { type SolverManager struct {
core *api.Core core *api.Core
@ -29,55 +28,12 @@ type SolverManager struct {
} }
func NewSolversManager(core *api.Core) *SolverManager { func NewSolversManager(core *api.Core) *SolverManager {
solvers := map[challenge.Type]solver{
challenge.HTTP01: http01.NewChallenge(core, validate, &http01.ProviderServer{}),
challenge.TLSALPN01: tlsalpn01.NewChallenge(core, validate, &tlsalpn01.ProviderServer{}),
}
return &SolverManager{ return &SolverManager{
solvers: solvers, solvers: map[challenge.Type]solver{},
core: core, core: core,
} }
} }
// SetHTTP01Address specifies a custom interface:port to be used for HTTP based challenges.
// If this option is not used, the default port 80 and all interfaces will be used.
// To only specify a port and no interface use the ":port" notation.
//
// NOTE: This REPLACES any custom HTTP provider previously set by calling
// c.SetProvider with the default HTTP challenge provider.
func (c *SolverManager) SetHTTP01Address(iface string) error {
host, port, err := net.SplitHostPort(iface)
if err != nil {
return err
}
if chlng, ok := c.solvers[challenge.HTTP01]; ok {
chlng.(*http01.Challenge).SetProvider(http01.NewProviderServer(host, port))
}
return nil
}
// SetTLSALPN01Address specifies a custom interface:port to be used for TLS based challenges.
// If this option is not used, the default port 443 and all interfaces will be used.
// To only specify a port and no interface use the ":port" notation.
//
// NOTE: This REPLACES any custom TLS-ALPN provider previously set by calling
// c.SetProvider with the default TLS-ALPN challenge provider.
func (c *SolverManager) SetTLSALPN01Address(iface string) error {
host, port, err := net.SplitHostPort(iface)
if err != nil {
return err
}
if chlng, ok := c.solvers[challenge.TLSALPN01]; ok {
chlng.(*tlsalpn01.Challenge).SetProvider(tlsalpn01.NewProviderServer(host, port))
}
return nil
}
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge. // SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error { func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error {
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p) c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p)
@ -96,18 +52,15 @@ func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.Cha
return nil return nil
} }
// Exclude explicitly removes challenges from the pool for solving. // Remove Remove a challenge type from the available solvers.
func (c *SolverManager) Exclude(challenges []challenge.Type) { func (c *SolverManager) Remove(chlgType challenge.Type) {
// Loop through all challenges and delete the requested one if found. delete(c.solvers, chlgType)
for _, chlg := range challenges {
delete(c.solvers, chlg)
}
} }
// Checks all challenges from the server in order and returns the first matching solver. // Checks all challenges from the server in order and returns the first matching solver.
func (c *SolverManager) chooseSolver(authz acme.Authorization) solver { func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
// Allow to have a deterministic challenge order // Allow to have a deterministic challenge order
sort.Sort(sort.Reverse(byType(authz.Challenges))) sort.Sort(byType(authz.Challenges))
domain := challenge.GetTargetedDomain(authz) domain := challenge.GetTargetedDomain(authz)
for _, chlg := range authz.Challenges { for _, chlg := range authz.Challenges {

View file

@ -5,54 +5,30 @@ import (
"crypto/rsa" "crypto/rsa"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"reflect" "sort"
"testing" "testing"
"unsafe"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
"github.com/xenolf/lego/acme/api" "github.com/xenolf/lego/acme/api"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/http01"
"github.com/xenolf/lego/platform/tester" "github.com/xenolf/lego/platform/tester"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
) )
func TestSolverManager_SetHTTP01Address(t *testing.T) { func TestByType(t *testing.T) {
_, apiURL, tearDown := tester.SetupFakeAPI() challenges := []acme.Challenge{
defer tearDown() {Type: "dns-01"}, {Type: "tlsalpn-01"}, {Type: "http-01"},
}
keyBits := 32 // small value keeps test fast sort.Sort(byType(challenges))
key, err := rsa.GenerateKey(rand.Reader, keyBits)
require.NoError(t, err, "Could not generate test key")
core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) expected := []acme.Challenge{
require.NoError(t, err) {Type: "tlsalpn-01"}, {Type: "http-01"}, {Type: "dns-01"},
}
solversManager := NewSolversManager(core) assert.Equal(t, expected, challenges)
optPort := "1234"
optHost := ""
err = solversManager.SetHTTP01Address(net.JoinHostPort(optHost, optPort))
require.NoError(t, err)
require.IsType(t, &http01.Challenge{}, solversManager.solvers[challenge.HTTP01])
httpSolver := solversManager.solvers[challenge.HTTP01].(*http01.Challenge)
httpProviderServer := (*http01.ProviderServer)(unsafe.Pointer(reflect.ValueOf(httpSolver).Elem().FieldByName("provider").InterfaceData()[1]))
assert.Equal(t, net.JoinHostPort(optHost, optPort), httpProviderServer.GetAddress())
// test setting different host
optHost = "127.0.0.1"
err = solversManager.SetHTTP01Address(net.JoinHostPort(optHost, optPort))
require.NoError(t, err)
httpProviderServer = (*http01.ProviderServer)(unsafe.Pointer(reflect.ValueOf(httpSolver).Elem().FieldByName("provider").InterfaceData()[1]))
assert.Equal(t, net.JoinHostPort(optHost, optPort), httpProviderServer.GetAddress())
} }
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {

View file

@ -107,7 +107,7 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tglesys:\tGLESYS_POLLING_INTERVAL, GLESYS_PROPAGATION_TIMEOUT, GLESYS_TTL, GLESYS_HTTP_TIMEOUT") fmt.Fprintln(w, "\tglesys:\tGLESYS_POLLING_INTERVAL, GLESYS_PROPAGATION_TIMEOUT, GLESYS_TTL, GLESYS_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tgodaddy:\tGODADDY_POLLING_INTERVAL, GODADDY_PROPAGATION_TIMEOUT, GODADDY_TTL, GODADDY_HTTP_TIMEOUT, GODADDY_SEQUENCE_INTERVAL") fmt.Fprintln(w, "\tgodaddy:\tGODADDY_POLLING_INTERVAL, GODADDY_PROPAGATION_TIMEOUT, GODADDY_TTL, GODADDY_HTTP_TIMEOUT, GODADDY_SEQUENCE_INTERVAL")
fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_POLLING_INTERVAL, HOSTINGDE_PROPAGATION_TIMEOUT, HOSTINGDE_TTL, HOSTINGDE_HTTP_TIMEOUT") fmt.Fprintln(w, "\thostingde:\tHOSTINGDE_POLLING_INTERVAL, HOSTINGDE_PROPAGATION_TIMEOUT, HOSTINGDE_TTL, HOSTINGDE_HTTP_TIMEOUT")
fmt.Fprintln(w, "\thttpreq:\t,HTTPREQ_POLLING_INTERVAL, HTTPREQ_PROPAGATION_TIMEOUT, HTTPREQ_HTTP_TIMEOUT") fmt.Fprintln(w, "\thttpreq:\tHTTPREQ_POLLING_INTERVAL, HTTPREQ_PROPAGATION_TIMEOUT, HTTPREQ_HTTP_TIMEOUT")
fmt.Fprintln(w, "\tiij:\tIIJ_POLLING_INTERVAL, IIJ_PROPAGATION_TIMEOUT, IIJ_TTL") fmt.Fprintln(w, "\tiij:\tIIJ_POLLING_INTERVAL, IIJ_PROPAGATION_TIMEOUT, IIJ_TTL")
fmt.Fprintln(w, "\tinwx:\tINWX_POLLING_INTERVAL, INWX_PROPAGATION_TIMEOUT, INWX_TTL, INWX_SANDBOX") fmt.Fprintln(w, "\tinwx:\tINWX_POLLING_INTERVAL, INWX_PROPAGATION_TIMEOUT, INWX_TTL, INWX_SANDBOX")
fmt.Fprintln(w, "\tlightsail:\tLIGHTSAIL_POLLING_INTERVAL, LIGHTSAIL_PROPAGATION_TIMEOUT") fmt.Fprintln(w, "\tlightsail:\tLIGHTSAIL_POLLING_INTERVAL, LIGHTSAIL_PROPAGATION_TIMEOUT")

View file

@ -13,8 +13,8 @@ func CreateFlags(defaultPath string) []cli.Flag {
}, },
cli.StringFlag{ cli.StringFlag{
Name: "server, s", Name: "server, s",
Value: lego.LEDirectoryProduction,
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.", Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
Value: lego.LEDirectoryProduction,
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "accept-tos, a", Name: "accept-tos, a",
@ -26,7 +26,7 @@ func CreateFlags(defaultPath string) []cli.Flag {
}, },
cli.StringFlag{ cli.StringFlag{
Name: "csr, c", Name: "csr, c",
Usage: "Certificate signing request filename, if an external CSR is to be used", Usage: "Certificate signing request filename, if an external CSR is to be used.",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "eab", Name: "eab",
@ -43,56 +43,63 @@ func CreateFlags(defaultPath string) []cli.Flag {
cli.StringFlag{ cli.StringFlag{
Name: "key-type, k", Name: "key-type, k",
Value: "rsa2048", Value: "rsa2048",
Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384", Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384.",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "filename", Name: "filename",
Usage: "Filename of the generated certificate", Usage: "(deprecated) Filename of the generated certificate.",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "path", Name: "path",
Usage: "Directory to use for storing the data", Usage: "Directory to use for storing the data.",
Value: defaultPath, Value: defaultPath,
}, },
cli.StringSliceFlag{ cli.BoolFlag{
Name: "exclude, x", Name: "http",
Usage: "Explicitly disallow solvers by name from being used. Solvers: \"http-01\", \"dns-01\", \"tls-alpn-01\".", Usage: "Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges.",
},
cli.IntFlag{
Name: "http-timeout",
Usage: "Set the HTTP timeout value to a specific value in seconds. The default is 10 seconds.",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "webroot", Name: "http.port",
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge", Usage: "Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port.",
Value: ":80",
},
cli.StringFlag{
Name: "http.webroot",
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge.",
}, },
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "memcached-host", Name: "http.memcached-host",
Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.", Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.",
}, },
cli.StringFlag{ cli.BoolFlag{
Name: "http", Name: "tls",
Usage: "Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port", Usage: "Use the TLS challenge to solve challenges. Can be mixed with other types of challenges.",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "tls", Name: "tls.port",
Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port", Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port.",
Value: ":443",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "dns", Name: "dns",
Usage: "Solve a DNS challenge using the specified provider. Disables all other challenges. Run 'lego dnshelp' for help on usage.", Usage: "Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "dns-disable-cp", Name: "dns.disable-cp",
Usage: "By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.", Usage: "By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.",
}, },
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "dns-resolvers", Name: "dns.resolvers",
Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.", Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
}, },
cli.IntFlag{
Name: "http-timeout",
Usage: "Set the HTTP timeout value to a specific value in seconds.",
},
cli.IntFlag{ cli.IntFlag{
Name: "dns-timeout", Name: "dns-timeout",
Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries. The default is 10 seconds.", Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries.",
Value: 10,
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "pem", Name: "pem",

View file

@ -3,8 +3,10 @@
package main package main
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/xenolf/lego/cmd" "github.com/xenolf/lego/cmd"
@ -20,7 +22,12 @@ func main() {
app.Name = "lego" app.Name = "lego"
app.HelpName = "lego" app.HelpName = "lego"
app.Usage = "Let's Encrypt client written in Go" app.Usage = "Let's Encrypt client written in Go"
app.EnableBashCompletion = true
app.Version = version app.Version = version
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("lego version %s %s/%s\n", c.App.Version, runtime.GOOS, runtime.GOARCH)
}
defaultPath := "" defaultPath := ""
cwd, err := os.Getwd() cwd, err := os.Getwd()

View file

@ -1,12 +1,15 @@
package cmd package cmd
import ( import (
"net"
"strings" "strings"
"time" "time"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/xenolf/lego/challenge" "github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/dns01" "github.com/xenolf/lego/challenge/dns01"
"github.com/xenolf/lego/challenge/http01"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/lego" "github.com/xenolf/lego/lego"
"github.com/xenolf/lego/log" "github.com/xenolf/lego/log"
"github.com/xenolf/lego/providers/dns" "github.com/xenolf/lego/providers/dns"
@ -15,24 +18,22 @@ import (
) )
func setupChallenges(ctx *cli.Context, client *lego.Client) { func setupChallenges(ctx *cli.Context, client *lego.Client) {
if len(ctx.GlobalStringSlice("exclude")) > 0 { if !ctx.GlobalBool("http") && !ctx.GlobalBool("tls") && !ctx.GlobalIsSet("dns") {
excludedSolvers(ctx, client) log.Fatal("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`.")
} }
if ctx.GlobalIsSet("webroot") { if ctx.GlobalBool("http") {
setupWebroot(client, ctx.GlobalString("webroot")) err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx))
if err != nil {
log.Fatal(err)
}
} }
if ctx.GlobalIsSet("memcached-host") { if ctx.GlobalBool("tls") {
setupMemcached(client, ctx.GlobalStringSlice("memcached-host")) err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
} if err != nil {
log.Fatal(err)
if ctx.GlobalIsSet("http") { }
setupHTTP(client, ctx.GlobalString("http"))
}
if ctx.GlobalIsSet("tls") {
setupTLS(client, ctx.GlobalString("tls"))
} }
if ctx.GlobalIsSet("dns") { if ctx.GlobalIsSet("dns") {
@ -40,65 +41,59 @@ func setupChallenges(ctx *cli.Context, client *lego.Client) {
} }
} }
func excludedSolvers(ctx *cli.Context, client *lego.Client) { func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
var cc []challenge.Type switch {
for _, s := range ctx.GlobalStringSlice("exclude") { case ctx.GlobalIsSet("http.webroot"):
cc = append(cc, challenge.Type(s)) ps, err := webroot.NewHTTPProvider(ctx.GlobalString("http.webroot"))
} if err != nil {
client.Challenge.Exclude(cc) log.Fatal(err)
} }
return ps
case ctx.GlobalIsSet("http.memcached-host"):
ps, err := memcached.NewMemcachedProvider(ctx.GlobalStringSlice("http.memcached-host"))
if err != nil {
log.Fatal(err)
}
return ps
case ctx.GlobalIsSet("http.port"):
iface := ctx.GlobalString("http.port")
if !strings.Contains(iface, ":") {
log.Fatalf("The --http switch only accepts interface:port or :port for its argument.")
}
func setupWebroot(client *lego.Client, path string) { host, port, err := net.SplitHostPort(iface)
provider, err := webroot.NewHTTPProvider(path) if err != nil {
if err != nil { log.Fatal(err)
log.Fatal(err) }
}
err = client.Challenge.SetHTTP01Provider(provider) return http01.NewProviderServer(host, port)
if err != nil { case ctx.GlobalBool("http"):
log.Fatal(err) return http01.NewProviderServer("", "")
} default:
log.Fatal("Invalid HTTP challenge options.")
// --webroot=foo indicates that the user specifically want to do a HTTP challenge return nil
// infer that the user also wants to exclude all other challenges
client.Challenge.Exclude([]challenge.Type{challenge.DNS01, challenge.TLSALPN01})
}
func setupMemcached(client *lego.Client, hosts []string) {
provider, err := memcached.NewMemcachedProvider(hosts)
if err != nil {
log.Fatal(err)
}
err = client.Challenge.SetHTTP01Provider(provider)
if err != nil {
log.Fatal(err)
}
// --memcached-host=foo:11211 indicates that the user specifically want to do a HTTP challenge
// infer that the user also wants to exclude all other challenges
client.Challenge.Exclude([]challenge.Type{challenge.DNS01, challenge.TLSALPN01})
}
func setupHTTP(client *lego.Client, iface string) {
if !strings.Contains(iface, ":") {
log.Fatalf("The --http switch only accepts interface:port or :port for its argument.")
}
err := client.Challenge.SetHTTP01Address(iface)
if err != nil {
log.Fatal(err)
} }
} }
func setupTLS(client *lego.Client, iface string) { func setupTLSProvider(ctx *cli.Context) challenge.Provider {
if !strings.Contains(iface, ":") { switch {
log.Fatalf("The --tls switch only accepts interface:port or :port for its argument.") case ctx.GlobalIsSet("tls.port"):
} iface := ctx.GlobalString("tls.port")
if !strings.Contains(iface, ":") {
log.Fatalf("The --tls switch only accepts interface:port or :port for its argument.")
}
err := client.Challenge.SetTLSALPN01Address(iface) host, port, err := net.SplitHostPort(iface)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
}
return tlsalpn01.NewProviderServer(host, port)
case ctx.GlobalBool("tls"):
return tlsalpn01.NewProviderServer("", "")
default:
log.Fatal("Invalid HTTP challenge options.")
return nil
} }
} }
@ -108,11 +103,11 @@ func setupDNS(ctx *cli.Context, client *lego.Client) {
log.Fatal(err) log.Fatal(err)
} }
servers := ctx.GlobalStringSlice("dns-resolvers") servers := ctx.GlobalStringSlice("dns.resolvers")
err = client.Challenge.SetDNS01Provider(provider, err = client.Challenge.SetDNS01Provider(provider,
dns01.CondOption(len(servers) > 0, dns01.CondOption(len(servers) > 0,
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.GlobalStringSlice("dns-resolvers")))), dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.GlobalStringSlice("dns.resolvers")))),
dns01.CondOption(ctx.GlobalIsSet("dns-disable-cp"), dns01.CondOption(ctx.GlobalIsSet("dns.disable-cp"),
dns01.DisableCompletePropagationRequirement()), dns01.DisableCompletePropagationRequirement()),
dns01.CondOption(ctx.GlobalIsSet("dns-timeout"), dns01.CondOption(ctx.GlobalIsSet("dns-timeout"),
dns01.AddDNSTimeout(time.Duration(ctx.GlobalInt("dns-timeout"))*time.Second)), dns01.AddDNSTimeout(time.Duration(ctx.GlobalInt("dns-timeout"))*time.Second)),

View file

@ -13,7 +13,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/xenolf/lego/certificate" "github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge" "github.com/xenolf/lego/challenge/http01"
"github.com/xenolf/lego/challenge/tlsalpn01"
"github.com/xenolf/lego/e2e/loader" "github.com/xenolf/lego/e2e/loader"
"github.com/xenolf/lego/lego" "github.com/xenolf/lego/lego"
"github.com/xenolf/lego/registration" "github.com/xenolf/lego/registration"
@ -50,12 +51,10 @@ func TestChallengeHTTP_Run(t *testing.T) {
output, err := load.RunLego( output, err := load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "tls-alpn-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-d", "acme.wtf", "-d", "acme.wtf",
"--http", ":5002", "--http",
"--tls", ":5001", "--http.port", ":5002",
"run") "run")
if len(output) > 0 { if len(output) > 0 {
@ -72,12 +71,10 @@ func TestChallengeTLS_Run_Domains(t *testing.T) {
output, err := load.RunLego( output, err := load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "http-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-d", "acme.wtf", "-d", "acme.wtf",
"--http", ":5002", "--tls",
"--tls", ":5001", "--tls.port", ":5001",
"run") "run")
if len(output) > 0 { if len(output) > 0 {
@ -94,12 +91,10 @@ func TestChallengeTLS_Run_CSR(t *testing.T) {
output, err := load.RunLego( output, err := load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "http-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-csr", "./fixtures/csr.raw", "-csr", "./fixtures/csr.raw",
"--http", ":5002", "--tls",
"--tls", ":5001", "--tls.port", ":5001",
"run") "run")
if len(output) > 0 { if len(output) > 0 {
@ -116,12 +111,10 @@ func TestChallengeTLS_Run_CSR_PEM(t *testing.T) {
output, err := load.RunLego( output, err := load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "http-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-csr", "./fixtures/csr.cert", "-csr", "./fixtures/csr.cert",
"--http", ":5002", "--tls",
"--tls", ":5001", "--tls.port", ":5001",
"run") "run")
if len(output) > 0 { if len(output) > 0 {
@ -138,13 +131,11 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) {
output, err := load.RunLego( output, err := load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "http-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-d", "lego.wtf", "-d", "lego.wtf",
"-d", "acme.lego.wtf", "-d", "acme.lego.wtf",
"--http", ":5002", "--tls",
"--tls", ":5001", "--tls.port", ":5001",
"run") "run")
if len(output) > 0 { if len(output) > 0 {
@ -157,12 +148,10 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) {
output, err = load.RunLego( output, err = load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "http-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-d", "lego.wtf", "-d", "lego.wtf",
"--http", ":5002", "--tls",
"--tls", ":5001", "--tls.port", ":5001",
"revoke") "revoke")
if len(output) > 0 { if len(output) > 0 {
@ -179,12 +168,10 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) {
output, err := load.RunLego( output, err := load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "http-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-d", "légô.wtf", "-d", "légô.wtf",
"--http", ":5002", "--tls",
"--tls", ":5001", "--tls.port", ":5001",
"run") "run")
if len(output) > 0 { if len(output) > 0 {
@ -197,12 +184,10 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) {
output, err = load.RunLego( output, err = load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "dns-01",
"-x", "http-01",
"-s", "https://localhost:14000/dir", "-s", "https://localhost:14000/dir",
"-d", "légô.wtf", "-d", "légô.wtf",
"--http", ":5002", "--tls",
"--tls", ":5001", "--tls.port", ":5001",
"revoke") "revoke")
if len(output) > 0 { if len(output) > 0 {
@ -228,8 +213,7 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) {
client, err := lego.NewClient(config) client, err := lego.NewClient(config)
require.NoError(t, err) require.NoError(t, err)
client.Challenge.Exclude([]challenge.Type{challenge.DNS01, challenge.TLSALPN01}) err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002"))
err = client.Challenge.SetHTTP01Address(":5002")
require.NoError(t, err) require.NoError(t, err)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
@ -267,8 +251,7 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) {
client, err := lego.NewClient(config) client, err := lego.NewClient(config)
require.NoError(t, err) require.NoError(t, err)
client.Challenge.Exclude([]challenge.Type{challenge.DNS01, challenge.HTTP01}) err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5001"))
err = client.Challenge.SetTLSALPN01Address(":5001")
require.NoError(t, err) require.NoError(t, err)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
@ -307,8 +290,7 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) {
client, err := lego.NewClient(config) client, err := lego.NewClient(config)
require.NoError(t, err) require.NoError(t, err)
client.Challenge.Exclude([]challenge.Type{challenge.DNS01, challenge.HTTP01}) err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5001"))
err = client.Challenge.SetTLSALPN01Address(":5001")
require.NoError(t, err) require.NoError(t, err)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})

View file

@ -11,7 +11,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/xenolf/lego/certificate" "github.com/xenolf/lego/certificate"
"github.com/xenolf/lego/challenge"
"github.com/xenolf/lego/challenge/dns01" "github.com/xenolf/lego/challenge/dns01"
"github.com/xenolf/lego/e2e/loader" "github.com/xenolf/lego/e2e/loader"
"github.com/xenolf/lego/lego" "github.com/xenolf/lego/lego"
@ -55,16 +54,12 @@ func TestChallengeDNS_Run(t *testing.T) {
output, err := load.RunLego( output, err := load.RunLego(
"-m", "hubert@hubert.com", "-m", "hubert@hubert.com",
"--accept-tos", "--accept-tos",
"-x", "http-01",
"-x", "tls-alpn-01",
"--dns-disable-cp",
"--dns-resolvers", ":8053",
"--dns", "exec", "--dns", "exec",
"--dns.resolvers", ":8053",
"--dns.disable-cp",
"-s", "https://localhost:15000/dir", "-s", "https://localhost:15000/dir",
"-d", "*.légo.acme", "-d", "*.légo.acme",
"-d", "légo.acme", "-d", "légo.acme",
"--http", ":5004",
"--tls", ":5003",
"run") "run")
if len(output) > 0 { if len(output) > 0 {
@ -100,7 +95,6 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) {
err = client.Challenge.SetDNS01Provider(provider, err = client.Challenge.SetDNS01Provider(provider,
dns01.AddRecursiveNameservers([]string{":8053"}), dns01.AddRecursiveNameservers([]string{":8053"}),
dns01.DisableCompletePropagationRequirement()) dns01.DisableCompletePropagationRequirement())
client.Challenge.Exclude([]challenge.Type{challenge.HTTP01, challenge.TLSALPN01})
require.NoError(t, err) require.NoError(t, err)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})