From fdf059fbbd3431b9df7f052cfedba7e130a02bf4 Mon Sep 17 00:00:00 2001 From: Adrien Carbonne Date: Wed, 10 Feb 2016 12:19:29 +0100 Subject: [PATCH 1/7] Added a --webroot option for HTTP challenge When using this option, the challenge will be written in a file in ".well-known/acme-challenge/" inside the given webroot folder. This allows lego to work without binding any port at all. --- README.md | 4 ++- acme/client.go | 16 ++++++++++++ acme/http_challenge_test.go | 46 ++++++++++++++++++++++++++++++++++ acme/http_challenge_webroot.go | 34 +++++++++++++++++++++++++ cli.go | 4 +++ cli_handlers.go | 3 +++ 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 acme/http_challenge_webroot.go diff --git a/README.md b/README.md index 00ce417b..99590798 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,11 @@ When using the standard `--path` option, all certificates and account configurat #### Sudo The CLI does not require root permissions but needs to bind to port 80 and 443 for certain challenges. -To run the CLI without sudo, you have two options: +To run the CLI without sudo, you have three options: - 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 `--webport` 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. #### Port Usage By default lego assumes it is able to bind to ports 80 and 443 to solve challenges. @@ -87,6 +88,7 @@ GLOBAL OPTIONS: --rsa-key-size, -B "2048" Size of the RSA key. --path "${CWD}/.lego" Directory to use for storing the data --exclude, -x [--exclude option --exclude option] Explicitly disallow solvers by name from being used. Solvers: "http-01", "tls-sni-01". + --webroot Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge --http Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port --tls Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port --dns Enable the DNS challenge for solving using a provider. diff --git a/acme/client.go b/acme/client.go index 700aeab5..119e0d94 100644 --- a/acme/client.go +++ b/acme/client.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "log" "net" + "os" "regexp" "strconv" "strings" @@ -116,6 +117,21 @@ func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider) return nil } +// SetWebRoot specifies a custom folder path to be used for HTTP based challenges. +// If this option is used, lego will not bind any port to listen to, +// instead it will write the challenge in a file into path/.well-known/acme-challenge/ +func (c *Client) SetWebRoot(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return err + } + + if chlng, ok := c.solvers[HTTP01]; ok { + chlng.(*httpChallenge).provider = &httpChallengeWebRoot{path: path} + } + + return nil +} + // SetHTTPAddress 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. diff --git a/acme/http_challenge_test.go b/acme/http_challenge_test.go index 9c33d7d0..ff41c209 100644 --- a/acme/http_challenge_test.go +++ b/acme/http_challenge_test.go @@ -3,6 +3,7 @@ package acme import ( "crypto/rsa" "io/ioutil" + "os" "strings" "testing" ) @@ -54,3 +55,48 @@ func TestHTTPChallengeInvalidPort(t *testing.T) { t.Errorf("Solve error: got %q, want suffix %q", err.Error(), want) } } + +func TestHTTPChallengeWebRoot(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 512) + j := &jws{privKey: privKey.(*rsa.PrivateKey)} + clientChallenge := challenge{Type: HTTP01, Token: "http1"} + mockValidate := func(_ *jws, _, _ string, chlng challenge) error { + challengeFilePath := "webroot/.well-known/acme-challenge/" + chlng.Token + + if _, err := os.Stat(challengeFilePath); os.IsNotExist(err) { + t.Error("Challenge file was not created in webroot") + } + + data, err := ioutil.ReadFile(challengeFilePath) + if err != nil { + return err + } + dataStr := string(data) + + if dataStr != chlng.KeyAuthorization { + t.Errorf("Challenge file content: got %q, want %q", dataStr, chlng.KeyAuthorization) + } + + return nil + } + solver := &httpChallenge{jws: j, validate: mockValidate, provider: &httpChallengeWebRoot{path: "webroot"}} + + os.MkdirAll("webroot/.well-known/acme-challenge", 0777) + if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil { + t.Errorf("Solve error: got %v, want nil", err) + } + defer os.RemoveAll("webroot") +} + +func TestHTTPChallengeWebRootInvalidPath(t *testing.T) { + privKey, _ := generatePrivateKey(rsakey, 128) + j := &jws{privKey: privKey.(*rsa.PrivateKey)} + clientChallenge := challenge{Type: HTTP01, Token: "http2"} + solver := &httpChallenge{jws: j, validate: stubValidate, provider: &httpChallengeWebRoot{path: "/invalid-path-123456"}} + + if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil { + t.Errorf("Solve error: got %v, want error", err) + } else if want := "Could not write file in webroot"; !strings.Contains(err.Error(), want) { + t.Errorf("Solve error: got %q, want content %q", err.Error(), want) + } +} diff --git a/acme/http_challenge_webroot.go b/acme/http_challenge_webroot.go new file mode 100644 index 00000000..caece376 --- /dev/null +++ b/acme/http_challenge_webroot.go @@ -0,0 +1,34 @@ +package acme + +import ( + "fmt" + "io/ioutil" + "os" + "path" +) + +// httpChallengeWebRoot implements ChallengeProvider for `http-01` challenge +type httpChallengeWebRoot struct { + path string +} + +// Present makes the token available at `HTTP01ChallengePath(token)` +func (w *httpChallengeWebRoot) Present(domain, token, keyAuth string) error { + var err error + err = ioutil.WriteFile(path.Join(w.path, HTTP01ChallengePath(token)), []byte(keyAuth), 0777) + if err != nil { + return fmt.Errorf("Could not write file in webroot for HTTP challenge -> %v", err) + } + + return nil +} + +func (w *httpChallengeWebRoot) CleanUp(domain, token, keyAuth string) error { + var err error + err = os.Remove(path.Join(w.path, HTTP01ChallengePath(token))) + if err != nil { + return fmt.Errorf("Could not remove file in webroot after HTTP challenge -> %v", err) + } + + return nil +} diff --git a/cli.go b/cli.go index 3851c455..f0edccf5 100644 --- a/cli.go +++ b/cli.go @@ -102,6 +102,10 @@ func main() { Name: "exclude, x", Usage: "Explicitly disallow solvers by name from being used. Solvers: \"http-01\", \"tls-sni-01\".", }, + cli.StringFlag{ + Name: "webroot", + Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge", + }, cli.StringFlag{ Name: "http", Usage: "Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port", diff --git a/cli_handlers.go b/cli_handlers.go index 2b5b3fc0..d9da8357 100644 --- a/cli_handlers.go +++ b/cli_handlers.go @@ -43,6 +43,9 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { client.ExcludeChallenges(conf.ExcludedSolvers()) } + if c.GlobalIsSet("webroot") { + client.SetWebRoot(c.GlobalString("webroot")) + } if c.GlobalIsSet("http") { client.SetHTTPAddress(c.GlobalString("http")) } From 9a424abdee87489eca675c1a33955aaa5e7e0394 Mon Sep 17 00:00:00 2001 From: Adrien Carbonne Date: Wed, 10 Feb 2016 16:55:10 +0100 Subject: [PATCH 2/7] Using the standard provider way Other providers should be used with the SetChallengeProvider function and should supply a New function. In your case this would be NewHTTPProviderWebroot taking the path as an argument. --- acme/client.go | 16 ---------------- acme/http_challenge_test.go | 6 +++--- acme/http_challenge_webroot.go | 33 +++++++++++++++++++++++++++------ cli_handlers.go | 7 ++++++- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/acme/client.go b/acme/client.go index 119e0d94..700aeab5 100644 --- a/acme/client.go +++ b/acme/client.go @@ -12,7 +12,6 @@ import ( "io/ioutil" "log" "net" - "os" "regexp" "strconv" "strings" @@ -117,21 +116,6 @@ func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider) return nil } -// SetWebRoot specifies a custom folder path to be used for HTTP based challenges. -// If this option is used, lego will not bind any port to listen to, -// instead it will write the challenge in a file into path/.well-known/acme-challenge/ -func (c *Client) SetWebRoot(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - return err - } - - if chlng, ok := c.solvers[HTTP01]; ok { - chlng.(*httpChallenge).provider = &httpChallengeWebRoot{path: path} - } - - return nil -} - // SetHTTPAddress 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. diff --git a/acme/http_challenge_test.go b/acme/http_challenge_test.go index ff41c209..56f07345 100644 --- a/acme/http_challenge_test.go +++ b/acme/http_challenge_test.go @@ -79,7 +79,7 @@ func TestHTTPChallengeWebRoot(t *testing.T) { return nil } - solver := &httpChallenge{jws: j, validate: mockValidate, provider: &httpChallengeWebRoot{path: "webroot"}} + solver := &httpChallenge{jws: j, validate: mockValidate, provider: &HTTPProviderWebroot{path: "webroot"}} os.MkdirAll("webroot/.well-known/acme-challenge", 0777) if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil { @@ -92,11 +92,11 @@ func TestHTTPChallengeWebRootInvalidPath(t *testing.T) { privKey, _ := generatePrivateKey(rsakey, 128) j := &jws{privKey: privKey.(*rsa.PrivateKey)} clientChallenge := challenge{Type: HTTP01, Token: "http2"} - solver := &httpChallenge{jws: j, validate: stubValidate, provider: &httpChallengeWebRoot{path: "/invalid-path-123456"}} + solver := &httpChallenge{jws: j, validate: stubValidate, provider: &HTTPProviderWebroot{path: "/invalid-\000-path"}} if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil { t.Errorf("Solve error: got %v, want error", err) - } else if want := "Could not write file in webroot"; !strings.Contains(err.Error(), want) { + } else if want := "Could not create required directories in webroot"; !strings.Contains(err.Error(), want) { t.Errorf("Solve error: got %q, want content %q", err.Error(), want) } } diff --git a/acme/http_challenge_webroot.go b/acme/http_challenge_webroot.go index caece376..914d2545 100644 --- a/acme/http_challenge_webroot.go +++ b/acme/http_challenge_webroot.go @@ -7,15 +7,35 @@ import ( "path" ) -// httpChallengeWebRoot implements ChallengeProvider for `http-01` challenge -type httpChallengeWebRoot struct { +// HTTPProviderWebroot implements ChallengeProvider for `http-01` challenge +type HTTPProviderWebroot struct { path string } -// Present makes the token available at `HTTP01ChallengePath(token)` -func (w *httpChallengeWebRoot) Present(domain, token, keyAuth string) error { +// NewHTTPProviderWebroot returns a HTTPProviderWebroot instance with a configured webroot path +func NewHTTPProviderWebroot(path string) (*HTTPProviderWebroot, error) { + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil, fmt.Errorf("Webroot path does not exist") + } + + c := &HTTPProviderWebroot{ + path: path, + } + + return c, nil +} + +// Present makes the token available at `HTTP01ChallengePath(token)` by creating a file in the given webroot path +func (w *HTTPProviderWebroot) Present(domain, token, keyAuth string) error { var err error - err = ioutil.WriteFile(path.Join(w.path, HTTP01ChallengePath(token)), []byte(keyAuth), 0777) + + challengeFilePath := path.Join(w.path, HTTP01ChallengePath(token)) + err = os.MkdirAll(path.Dir(challengeFilePath), 0777) + if err != nil { + return fmt.Errorf("Could not create required directories in webroot for HTTP challenge -> %v", err) + } + + err = ioutil.WriteFile(challengeFilePath, []byte(keyAuth), 0777) if err != nil { return fmt.Errorf("Could not write file in webroot for HTTP challenge -> %v", err) } @@ -23,7 +43,8 @@ func (w *httpChallengeWebRoot) Present(domain, token, keyAuth string) error { return nil } -func (w *httpChallengeWebRoot) CleanUp(domain, token, keyAuth string) error { +// CleanUp removes the file created for the challenge +func (w *HTTPProviderWebroot) CleanUp(domain, token, keyAuth string) error { var err error err = os.Remove(path.Join(w.path, HTTP01ChallengePath(token))) if err != nil { diff --git a/cli_handlers.go b/cli_handlers.go index d9da8357..eda355b5 100644 --- a/cli_handlers.go +++ b/cli_handlers.go @@ -44,7 +44,12 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { } if c.GlobalIsSet("webroot") { - client.SetWebRoot(c.GlobalString("webroot")) + provider, err := acme.NewHTTPProviderWebroot(c.GlobalString("webroot")) + if err != nil { + logger().Fatal(err) + } + + client.SetChallengeProvider(HTTP01, provider) } if c.GlobalIsSet("http") { client.SetHTTPAddress(c.GlobalString("http")) From 8850ac8bbaa36c2b4c8ae5d5a24ae9ae7825968d Mon Sep 17 00:00:00 2001 From: Adrien Carbonne Date: Wed, 10 Feb 2016 17:00:20 +0100 Subject: [PATCH 3/7] Minor fix, forgot package name --- cli_handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli_handlers.go b/cli_handlers.go index eda355b5..efd9e368 100644 --- a/cli_handlers.go +++ b/cli_handlers.go @@ -49,7 +49,7 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { logger().Fatal(err) } - client.SetChallengeProvider(HTTP01, provider) + client.SetChallengeProvider(acme.HTTP01, provider) } if c.GlobalIsSet("http") { client.SetHTTPAddress(c.GlobalString("http")) From f9ae3791c5600a28143a3a41ffa1b22a83685ca5 Mon Sep 17 00:00:00 2001 From: Adrien Carbonne Date: Sun, 13 Mar 2016 16:36:13 +0100 Subject: [PATCH 4/7] Fixed typo in readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99590798..a85a422c 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ To run the CLI without sudo, you have three options: - 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 `--webport` 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 `--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. #### Port Usage By default lego assumes it is able to bind to ports 80 and 443 to solve challenges. From 7ebad0e886a861ea04538a146d541e43d388453f Mon Sep 17 00:00:00 2001 From: Adrien Carbonne Date: Mon, 14 Mar 2016 11:49:02 +0100 Subject: [PATCH 5/7] Relocating provider to lego/providers/http/webroot --- acme/http_challenge_test.go | 46 ------------------- .../http/webroot/webroot.go | 9 ++-- providers/http/webroot/webroot_test.go | 46 +++++++++++++++++++ 3 files changed, 52 insertions(+), 49 deletions(-) rename acme/http_challenge_webroot.go => providers/http/webroot/webroot.go (81%) create mode 100644 providers/http/webroot/webroot_test.go diff --git a/acme/http_challenge_test.go b/acme/http_challenge_test.go index b1e77543..fdd8f4d2 100644 --- a/acme/http_challenge_test.go +++ b/acme/http_challenge_test.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "crypto/rsa" "io/ioutil" - "os" "strings" "testing" ) @@ -56,48 +55,3 @@ func TestHTTPChallengeInvalidPort(t *testing.T) { t.Errorf("Solve error: got %q, want suffix %q", err.Error(), want) } } - -func TestHTTPChallengeWebRoot(t *testing.T) { - privKey, _ := generatePrivateKey(rsakey, 512) - j := &jws{privKey: privKey.(*rsa.PrivateKey)} - clientChallenge := challenge{Type: HTTP01, Token: "http1"} - mockValidate := func(_ *jws, _, _ string, chlng challenge) error { - challengeFilePath := "webroot/.well-known/acme-challenge/" + chlng.Token - - if _, err := os.Stat(challengeFilePath); os.IsNotExist(err) { - t.Error("Challenge file was not created in webroot") - } - - data, err := ioutil.ReadFile(challengeFilePath) - if err != nil { - return err - } - dataStr := string(data) - - if dataStr != chlng.KeyAuthorization { - t.Errorf("Challenge file content: got %q, want %q", dataStr, chlng.KeyAuthorization) - } - - return nil - } - solver := &httpChallenge{jws: j, validate: mockValidate, provider: &HTTPProviderWebroot{path: "webroot"}} - - os.MkdirAll("webroot/.well-known/acme-challenge", 0777) - if err := solver.Solve(clientChallenge, "localhost:23457"); err != nil { - t.Errorf("Solve error: got %v, want nil", err) - } - defer os.RemoveAll("webroot") -} - -func TestHTTPChallengeWebRootInvalidPath(t *testing.T) { - privKey, _ := generatePrivateKey(rsakey, 128) - j := &jws{privKey: privKey.(*rsa.PrivateKey)} - clientChallenge := challenge{Type: HTTP01, Token: "http2"} - solver := &httpChallenge{jws: j, validate: stubValidate, provider: &HTTPProviderWebroot{path: "/invalid-\000-path"}} - - if err := solver.Solve(clientChallenge, "localhost:123456"); err == nil { - t.Errorf("Solve error: got %v, want error", err) - } else if want := "Could not create required directories in webroot"; !strings.Contains(err.Error(), want) { - t.Errorf("Solve error: got %q, want content %q", err.Error(), want) - } -} diff --git a/acme/http_challenge_webroot.go b/providers/http/webroot/webroot.go similarity index 81% rename from acme/http_challenge_webroot.go rename to providers/http/webroot/webroot.go index 914d2545..823f1260 100644 --- a/acme/http_challenge_webroot.go +++ b/providers/http/webroot/webroot.go @@ -1,10 +1,13 @@ -package acme +// Package webroot implements a HTTP provider for solving the HTTP-01 challenge using web server's root path. +package webroot import ( "fmt" "io/ioutil" "os" "path" + + "github.com/xenolf/lego/acme" ) // HTTPProviderWebroot implements ChallengeProvider for `http-01` challenge @@ -29,7 +32,7 @@ func NewHTTPProviderWebroot(path string) (*HTTPProviderWebroot, error) { func (w *HTTPProviderWebroot) Present(domain, token, keyAuth string) error { var err error - challengeFilePath := path.Join(w.path, HTTP01ChallengePath(token)) + challengeFilePath := path.Join(w.path, acme.HTTP01ChallengePath(token)) err = os.MkdirAll(path.Dir(challengeFilePath), 0777) if err != nil { return fmt.Errorf("Could not create required directories in webroot for HTTP challenge -> %v", err) @@ -46,7 +49,7 @@ func (w *HTTPProviderWebroot) Present(domain, token, keyAuth string) error { // CleanUp removes the file created for the challenge func (w *HTTPProviderWebroot) CleanUp(domain, token, keyAuth string) error { var err error - err = os.Remove(path.Join(w.path, HTTP01ChallengePath(token))) + err = os.Remove(path.Join(w.path, acme.HTTP01ChallengePath(token))) if err != nil { return fmt.Errorf("Could not remove file in webroot after HTTP challenge -> %v", err) } diff --git a/providers/http/webroot/webroot_test.go b/providers/http/webroot/webroot_test.go new file mode 100644 index 00000000..755d947b --- /dev/null +++ b/providers/http/webroot/webroot_test.go @@ -0,0 +1,46 @@ +package webroot + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestHTTPProviderWebRoot(t *testing.T) { + webroot := "webroot" + domain := "domain" + token := "token" + keyAuth := "keyAuth" + challengeFilePath := webroot + "/.well-known/acme-challenge/" + token + + os.MkdirAll(webroot+"/.well-known/acme-challenge", 0777) + defer os.RemoveAll(webroot) + + provider, err := NewHTTPProviderWebroot(webroot) + if err != nil { + t.Errorf("Webroot provider error: got %v, want nil", err) + } + + err = provider.Present(domain, token, keyAuth) + if err != nil { + t.Errorf("Webroot provider present() error: got %v, want nil", err) + } + + if _, err := os.Stat(challengeFilePath); os.IsNotExist(err) { + t.Error("Challenge file was not created in webroot") + } + + data, err := ioutil.ReadFile(challengeFilePath) + if err != nil { + t.Errorf("Webroot provider ReadFile() error: got %v, want nil", err) + } + dataStr := string(data) + if dataStr != keyAuth { + t.Errorf("Challenge file content: got %q, want %q", dataStr, keyAuth) + } + + err = provider.CleanUp(domain, token, keyAuth) + if err != nil { + t.Errorf("Webroot provider CleanUp() error: got %v, want nil", err) + } +} From 4116254e6caa27b97ae47a5d78ceaa6a06fe3348 Mon Sep 17 00:00:00 2001 From: Adrien Carbonne Date: Mon, 14 Mar 2016 11:52:45 +0100 Subject: [PATCH 6/7] Fixed CLI after WebRoot provider was moved --- cli_handlers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli_handlers.go b/cli_handlers.go index ab11d11d..4ab2e32a 100644 --- a/cli_handlers.go +++ b/cli_handlers.go @@ -16,6 +16,7 @@ import ( "github.com/xenolf/lego/providers/dns/dnsimple" "github.com/xenolf/lego/providers/dns/rfc2136" "github.com/xenolf/lego/providers/dns/route53" + "github.com/xenolf/lego/providers/http/webroot" ) func checkFolder(path string) error { @@ -54,7 +55,7 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { } if c.GlobalIsSet("webroot") { - provider, err := acme.NewHTTPProviderWebroot(c.GlobalString("webroot")) + provider, err := webroot.NewHTTPProviderWebroot(c.GlobalString("webroot")) if err != nil { logger().Fatal(err) } From 0886c37703152b40425ae3ee00aec58e35b7d4f2 Mon Sep 17 00:00:00 2001 From: Adrien Carbonne Date: Tue, 15 Mar 2016 11:38:23 +0100 Subject: [PATCH 7/7] Excluding DNS01 and TLSSNI01 challenges if --webroot is used --- cli_handlers.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cli_handlers.go b/cli_handlers.go index 4ab2e32a..0ef89441 100644 --- a/cli_handlers.go +++ b/cli_handlers.go @@ -61,6 +61,10 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { } client.SetChallengeProvider(acme.HTTP01, provider) + + // --webroot=foo indicates that the user specifically want to do a HTTP challenge + // infer that the user also wants to exclude all other challenges + client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01}) } if c.GlobalIsSet("http") { if strings.Index(c.GlobalString("http"), ":") == -1 {