Merge pull request #118 from adriencarbonne/master
Added a --webroot option for HTTP challenge
This commit is contained in:
commit
325db78c91
5 changed files with 124 additions and 1 deletions
|
@ -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
4
cli.go
|
@ -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",
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
58
providers/http/webroot/webroot.go
Normal file
58
providers/http/webroot/webroot.go
Normal 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
|
||||||
|
}
|
46
providers/http/webroot/webroot_test.go
Normal file
46
providers/http/webroot/webroot_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue