Merge pull request #118 from adriencarbonne/master

Added a --webroot option for HTTP challenge
This commit is contained in:
xenolf 2016-03-16 11:09:38 +01:00
commit 325db78c91
5 changed files with 124 additions and 1 deletions

View file

@ -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 `--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.
@ -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 Solve a DNS challenge using the specified provider. Disables all other solvers.

4
cli.go
View file

@ -116,6 +116,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",

View file

@ -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 {
@ -53,6 +54,18 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
client.ExcludeChallenges(conf.ExcludedSolvers())
}
if c.GlobalIsSet("webroot") {
provider, err := webroot.NewHTTPProviderWebroot(c.GlobalString("webroot"))
if err != nil {
logger().Fatal(err)
}
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 {
logger().Fatalf("The --http switch only accepts interface:port or :port for its argument.")

View file

@ -0,0 +1,58 @@
// 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
type HTTPProviderWebroot struct {
path string
}
// 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
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)
}
err = ioutil.WriteFile(challengeFilePath, []byte(keyAuth), 0777)
if err != nil {
return fmt.Errorf("Could not write file in webroot for HTTP challenge -> %v", err)
}
return nil
}
// 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, acme.HTTP01ChallengePath(token)))
if err != nil {
return fmt.Errorf("Could not remove file in webroot after HTTP challenge -> %v", err)
}
return nil
}

View file

@ -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)
}
}