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 #### Sudo
The CLI does not require root permissions but needs to bind to port 80 and 443 for certain challenges. 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 - 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` 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 #### 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.
@ -87,6 +88,7 @@ GLOBAL OPTIONS:
--rsa-key-size, -B "2048" Size of the RSA key. --rsa-key-size, -B "2048" Size of the RSA key.
--path "${CWD}/.lego" Directory to use for storing the data --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". --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 --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 --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. --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", Name: "exclude, x",
Usage: "Explicitly disallow solvers by name from being used. Solvers: \"http-01\", \"tls-sni-01\".", 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{ cli.StringFlag{
Name: "http", Name: "http",
Usage: "Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port", 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/dnsimple"
"github.com/xenolf/lego/providers/dns/rfc2136" "github.com/xenolf/lego/providers/dns/rfc2136"
"github.com/xenolf/lego/providers/dns/route53" "github.com/xenolf/lego/providers/dns/route53"
"github.com/xenolf/lego/providers/http/webroot"
) )
func checkFolder(path string) error { func checkFolder(path string) error {
@ -53,6 +54,18 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
client.ExcludeChallenges(conf.ExcludedSolvers()) 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 c.GlobalIsSet("http") {
if strings.Index(c.GlobalString("http"), ":") == -1 { if strings.Index(c.GlobalString("http"), ":") == -1 {
logger().Fatalf("The --http switch only accepts interface:port or :port for its argument.") 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)
}
}