forked from TrueCloudLab/lego
parent
e953bbc8b9
commit
4bb8bea031
6 changed files with 206 additions and 0 deletions
|
@ -3,6 +3,10 @@ go:
|
||||||
- 1.6.3
|
- 1.6.3
|
||||||
- 1.7
|
- 1.7
|
||||||
- tip
|
- tip
|
||||||
|
services:
|
||||||
|
- memcached
|
||||||
|
env:
|
||||||
|
- MEMCACHED_HOSTS=localhost:11211
|
||||||
install:
|
install:
|
||||||
- go get -t ./...
|
- go get -t ./...
|
||||||
script:
|
script:
|
||||||
|
|
4
cli.go
4
cli.go
|
@ -138,6 +138,10 @@ func main() {
|
||||||
Name: "webroot",
|
Name: "webroot",
|
||||||
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 webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge",
|
||||||
},
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "memcached-host",
|
||||||
|
Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.",
|
||||||
|
},
|
||||||
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",
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"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/dns/vultr"
|
"github.com/xenolf/lego/providers/dns/vultr"
|
||||||
|
"github.com/xenolf/lego/providers/http/memcached"
|
||||||
"github.com/xenolf/lego/providers/http/webroot"
|
"github.com/xenolf/lego/providers/http/webroot"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,6 +102,18 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
|
||||||
// infer that the user also wants to exclude all other challenges
|
// infer that the user also wants to exclude all other challenges
|
||||||
client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01})
|
client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01})
|
||||||
}
|
}
|
||||||
|
if c.GlobalIsSet("memcached-host") {
|
||||||
|
provider, err := memcached.NewMemcachedProvider(c.GlobalStringSlice("memcached-host"))
|
||||||
|
if err != nil {
|
||||||
|
logger().Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SetChallengeProvider(acme.HTTP01, provider)
|
||||||
|
|
||||||
|
// --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.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.")
|
||||||
|
|
15
providers/http/memcached/README.md
Normal file
15
providers/http/memcached/README.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Memcached http provider
|
||||||
|
|
||||||
|
Publishes challenges into memcached where they can be retrieved by nginx. Allows
|
||||||
|
specifying multiple memcached servers and the responses will be published to all
|
||||||
|
of them, making it easier to verify when your domain is hosted on a cluster of
|
||||||
|
servers.
|
||||||
|
|
||||||
|
Example nginx config:
|
||||||
|
|
||||||
|
```
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
set $memcached_key "$uri";
|
||||||
|
memcached_pass 127.0.0.1:11211;
|
||||||
|
}
|
||||||
|
```
|
59
providers/http/memcached/memcached.go
Normal file
59
providers/http/memcached/memcached.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Package webroot implements a HTTP provider for solving the HTTP-01 challenge using web server's root path.
|
||||||
|
package memcached
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/rainycape/memcache"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPProvider implements ChallengeProvider for `http-01` challenge
|
||||||
|
type MemcachedProvider struct {
|
||||||
|
hosts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPProvider returns a HTTPProvider instance with a configured webroot path
|
||||||
|
func NewMemcachedProvider(hosts []string) (*MemcachedProvider, error) {
|
||||||
|
if len(hosts) == 0 {
|
||||||
|
return nil, fmt.Errorf("No memcached hosts provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &MemcachedProvider{
|
||||||
|
hosts: hosts,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present makes the token available at `HTTP01ChallengePath(token)` by creating a file in the given webroot path
|
||||||
|
func (w *MemcachedProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
|
||||||
|
for _, host := range w.hosts {
|
||||||
|
mc, err := memcache.New(host)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mc.Add(&memcache.Item{
|
||||||
|
Key: challengePath,
|
||||||
|
Value: []byte(keyAuth),
|
||||||
|
Expiration: 60,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) == len(w.hosts) {
|
||||||
|
return fmt.Errorf("Unable to store key in any of the memcache hosts -> %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp removes the file created for the challenge
|
||||||
|
func (w *MemcachedProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
// Memcached will clean up itself, that's what expiration is for.
|
||||||
|
return nil
|
||||||
|
}
|
111
providers/http/memcached/memcached_test.go
Normal file
111
providers/http/memcached/memcached_test.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package memcached
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/rainycape/memcache"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
memcachedHosts []string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
domain = "lego.test"
|
||||||
|
token = "foo"
|
||||||
|
keyAuth = "bar"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
memcachedHostsStr := os.Getenv("MEMCACHED_HOSTS")
|
||||||
|
if len(memcachedHostsStr) > 0 {
|
||||||
|
memcachedHosts = strings.Split(memcachedHostsStr, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMemcachedProviderEmpty(t *testing.T) {
|
||||||
|
emptyHosts := make([]string, 0)
|
||||||
|
_, err := NewMemcachedProvider(emptyHosts)
|
||||||
|
assert.EqualError(t, err, "No memcached hosts provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMemcachedProviderValid(t *testing.T) {
|
||||||
|
if len(memcachedHosts) == 0 {
|
||||||
|
t.Skip("Skipping memcached tests")
|
||||||
|
}
|
||||||
|
_, err := NewMemcachedProvider(memcachedHosts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemcachedPresentSingleHost(t *testing.T) {
|
||||||
|
if len(memcachedHosts) == 0 {
|
||||||
|
t.Skip("Skipping memcached tests")
|
||||||
|
}
|
||||||
|
p, err := NewMemcachedProvider(memcachedHosts[0:1])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
|
||||||
|
|
||||||
|
err = p.Present(domain, token, keyAuth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
mc, err := memcache.New(memcachedHosts[0])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
i, err := mc.Get(challengePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, i.Value, []byte(keyAuth))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemcachedPresentMultiHost(t *testing.T) {
|
||||||
|
if len(memcachedHosts) <= 1 {
|
||||||
|
t.Skip("Skipping memcached multi-host tests")
|
||||||
|
}
|
||||||
|
p, err := NewMemcachedProvider(memcachedHosts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
|
||||||
|
|
||||||
|
err = p.Present(domain, token, keyAuth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
for _, host := range memcachedHosts {
|
||||||
|
mc, err := memcache.New(host)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
i, err := mc.Get(challengePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, i.Value, []byte(keyAuth))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemcachedPresentPartialFailureMultiHost(t *testing.T) {
|
||||||
|
if len(memcachedHosts) == 0 {
|
||||||
|
t.Skip("Skipping memcached tests")
|
||||||
|
}
|
||||||
|
hosts := append(memcachedHosts, "5.5.5.5:11211")
|
||||||
|
p, err := NewMemcachedProvider(hosts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
|
||||||
|
|
||||||
|
err = p.Present(domain, token, keyAuth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
for _, host := range memcachedHosts {
|
||||||
|
mc, err := memcache.New(host)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
i, err := mc.Get(challengePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, i.Value, []byte(keyAuth))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemcachedCleanup(t *testing.T) {
|
||||||
|
if len(memcachedHosts) == 0 {
|
||||||
|
t.Skip("Skipping memcached tests")
|
||||||
|
}
|
||||||
|
p, err := NewMemcachedProvider(memcachedHosts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, p.CleanUp(domain, token, keyAuth))
|
||||||
|
}
|
Loading…
Reference in a new issue