parent
e953bbc8b9
commit
4bb8bea031
6 changed files with 206 additions and 0 deletions
|
@ -3,6 +3,10 @@ go:
|
|||
- 1.6.3
|
||||
- 1.7
|
||||
- tip
|
||||
services:
|
||||
- memcached
|
||||
env:
|
||||
- MEMCACHED_HOSTS=localhost:11211
|
||||
install:
|
||||
- go get -t ./...
|
||||
script:
|
||||
|
|
4
cli.go
4
cli.go
|
@ -138,6 +138,10 @@ func main() {
|
|||
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.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{
|
||||
Name: "http",
|
||||
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/route53"
|
||||
"github.com/xenolf/lego/providers/dns/vultr"
|
||||
"github.com/xenolf/lego/providers/http/memcached"
|
||||
"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
|
||||
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 strings.Index(c.GlobalString("http"), ":") == -1 {
|
||||
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