2019-10-11 18:25:15 +03:00
|
|
|
package certmagic
|
|
|
|
|
|
|
|
import (
|
2019-10-17 18:31:02 +03:00
|
|
|
"net/http"
|
2019-10-15 21:32:20 +03:00
|
|
|
"os"
|
2019-10-16 14:58:14 +03:00
|
|
|
"reflect"
|
|
|
|
"sort"
|
2019-10-11 18:25:15 +03:00
|
|
|
"testing"
|
2019-10-16 14:58:14 +03:00
|
|
|
"time"
|
2019-10-11 18:25:15 +03:00
|
|
|
|
|
|
|
"github.com/go-acme/lego/v3/providers/dns/cloudflare"
|
2019-10-15 21:32:20 +03:00
|
|
|
"github.com/mholt/certmagic"
|
|
|
|
"github.com/micro/go-micro/api/server/acme"
|
2019-10-24 00:31:36 +03:00
|
|
|
cfstore "github.com/micro/go-micro/store/cloudflare"
|
2019-10-15 21:32:20 +03:00
|
|
|
"github.com/micro/go-micro/sync/lock/memory"
|
2019-10-11 18:25:15 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestCertMagic(t *testing.T) {
|
2019-10-15 21:32:20 +03:00
|
|
|
if len(os.Getenv("IN_TRAVIS_CI")) != 0 {
|
|
|
|
t.Skip("Travis doesn't let us bind :443")
|
|
|
|
}
|
|
|
|
l, err := New().NewListener()
|
|
|
|
if err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-15 21:32:20 +03:00
|
|
|
}
|
|
|
|
l.Close()
|
2019-10-11 18:25:15 +03:00
|
|
|
|
|
|
|
c := cloudflare.NewDefaultConfig()
|
|
|
|
c.AuthEmail = ""
|
|
|
|
c.AuthKey = ""
|
|
|
|
c.AuthToken = "test"
|
|
|
|
c.ZoneToken = "test"
|
|
|
|
|
2019-10-15 21:32:20 +03:00
|
|
|
p, err := cloudflare.NewDNSProviderConfig(c)
|
2019-10-11 18:25:15 +03:00
|
|
|
if err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-11 18:25:15 +03:00
|
|
|
}
|
|
|
|
|
2019-10-15 21:32:20 +03:00
|
|
|
l, err = New(acme.AcceptToS(true),
|
|
|
|
acme.CA(acme.LetsEncryptStagingCA),
|
|
|
|
acme.ChallengeProvider(p),
|
|
|
|
).NewListener()
|
2019-10-11 18:25:15 +03:00
|
|
|
|
2019-10-15 21:32:20 +03:00
|
|
|
if err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-15 21:32:20 +03:00
|
|
|
}
|
|
|
|
l.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestStorageImplementation(t *testing.T) {
|
2019-10-16 14:58:14 +03:00
|
|
|
apiToken, accountID := os.Getenv("CF_API_TOKEN"), os.Getenv("CF_ACCOUNT_ID")
|
|
|
|
kvID := os.Getenv("KV_NAMESPACE_ID")
|
|
|
|
if len(apiToken) == 0 || len(accountID) == 0 || len(kvID) == 0 {
|
|
|
|
t.Skip("No Cloudflare API keys available, skipping test")
|
|
|
|
}
|
|
|
|
|
2019-10-15 21:32:20 +03:00
|
|
|
var s certmagic.Storage
|
2019-10-24 00:31:36 +03:00
|
|
|
st, err := cfstore.NewStore(
|
|
|
|
cfstore.ApiToken(apiToken),
|
|
|
|
cfstore.AccountID(accountID),
|
|
|
|
cfstore.Namespace(kvID),
|
2019-10-16 14:58:14 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Couldn't initialise cloudflare storage: %s\n", err.Error())
|
|
|
|
}
|
2019-10-15 21:32:20 +03:00
|
|
|
s = &storage{
|
2019-10-16 14:58:14 +03:00
|
|
|
lock: memory.NewLock(),
|
|
|
|
store: st,
|
2019-10-15 21:32:20 +03:00
|
|
|
}
|
2019-10-16 14:58:14 +03:00
|
|
|
|
|
|
|
// Test Lock
|
2019-10-15 21:32:20 +03:00
|
|
|
if err := s.Lock("test"); err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err)
|
2019-10-15 21:32:20 +03:00
|
|
|
}
|
2019-10-16 14:58:14 +03:00
|
|
|
|
|
|
|
// Test Unlock
|
|
|
|
if err := s.Unlock("test"); err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err)
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test data
|
|
|
|
testdata := []struct {
|
|
|
|
key string
|
|
|
|
value []byte
|
|
|
|
}{
|
|
|
|
{key: "/foo/a", value: []byte("lorem")},
|
|
|
|
{key: "/foo/b", value: []byte("ipsum")},
|
|
|
|
{key: "/foo/c", value: []byte("dolor")},
|
|
|
|
{key: "/foo/d", value: []byte("sit")},
|
|
|
|
{key: "/bar/a", value: []byte("amet")},
|
|
|
|
{key: "/bar/b", value: []byte("consectetur")},
|
|
|
|
{key: "/bar/c", value: []byte("adipiscing")},
|
|
|
|
{key: "/bar/d", value: []byte("elit")},
|
|
|
|
{key: "/foo/bar/a", value: []byte("sed")},
|
|
|
|
{key: "/foo/bar/b", value: []byte("do")},
|
|
|
|
{key: "/foo/bar/c", value: []byte("eiusmod")},
|
|
|
|
{key: "/foo/bar/d", value: []byte("tempor")},
|
|
|
|
{key: "/foo/bar/baz/a", value: []byte("incididunt")},
|
|
|
|
{key: "/foo/bar/baz/b", value: []byte("ut")},
|
|
|
|
{key: "/foo/bar/baz/c", value: []byte("labore")},
|
|
|
|
{key: "/foo/bar/baz/d", value: []byte("et")},
|
|
|
|
// a duplicate just in case there's any edge cases
|
|
|
|
{key: "/foo/a", value: []byte("lorem")},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Store
|
|
|
|
for _, d := range testdata {
|
|
|
|
if err := s.Store(d.key, d.value); err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Load
|
|
|
|
for _, d := range testdata {
|
|
|
|
if value, err := s.Load(d.key); err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-16 14:58:14 +03:00
|
|
|
} else {
|
|
|
|
if !reflect.DeepEqual(value, d.value) {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatalf("Load %s: expected %v, got %v", d.key, d.value, value)
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Exists
|
|
|
|
for _, d := range testdata {
|
|
|
|
if !s.Exists(d.key) {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatalf("%s should exist, but doesn't\n", d.key)
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test List
|
|
|
|
if list, err := s.List("/", true); err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-16 14:58:14 +03:00
|
|
|
} else {
|
|
|
|
var expected []string
|
|
|
|
for i, d := range testdata {
|
|
|
|
if i != len(testdata)-1 {
|
|
|
|
// Don't store the intentionally duplicated key
|
|
|
|
expected = append(expected, d.key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Strings(expected)
|
|
|
|
sort.Strings(list)
|
|
|
|
if !reflect.DeepEqual(expected, list) {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatalf("List: Expected %v, got %v\n", expected, list)
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if list, err := s.List("/foo", false); err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-16 14:58:14 +03:00
|
|
|
} else {
|
|
|
|
sort.Strings(list)
|
|
|
|
expected := []string{"/foo/a", "/foo/b", "/foo/bar", "/foo/c", "/foo/d"}
|
|
|
|
if !reflect.DeepEqual(expected, list) {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatalf("List: expected %s, got %s\n", expected, list)
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Stat
|
|
|
|
for _, d := range testdata {
|
|
|
|
info, err := s.Stat(d.key)
|
|
|
|
if err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-16 14:58:14 +03:00
|
|
|
} else {
|
|
|
|
if info.Key != d.key {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatalf("Stat().Key: expected %s, got %s\n", d.key, info.Key)
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
if info.Size != int64(len(d.value)) {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatalf("Stat().Size: expected %d, got %d\n", len(d.value), info.Size)
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
if time.Since(info.Modified) > time.Minute {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatalf("Stat().Modified: expected time since last modified to be < 1 minute, got %v\n", time.Since(info.Modified))
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test Delete
|
|
|
|
for _, d := range testdata {
|
|
|
|
if err := s.Delete(d.key); err != nil {
|
2019-10-24 00:31:36 +03:00
|
|
|
t.Fatal(err.Error())
|
2019-10-16 14:58:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// New interface doesn't return an error, so call it in case any log.Fatal
|
|
|
|
// happens
|
2019-10-15 21:32:20 +03:00
|
|
|
New(acme.Cache(s))
|
2019-10-11 18:25:15 +03:00
|
|
|
}
|
2019-10-17 18:31:02 +03:00
|
|
|
|
|
|
|
// Full test with a real zone, with against LE staging
|
|
|
|
func TestE2e(t *testing.T) {
|
|
|
|
apiToken, accountID := os.Getenv("CF_API_TOKEN"), os.Getenv("CF_ACCOUNT_ID")
|
|
|
|
kvID := os.Getenv("KV_NAMESPACE_ID")
|
|
|
|
if len(apiToken) == 0 || len(accountID) == 0 || len(kvID) == 0 {
|
|
|
|
t.Skip("No Cloudflare API keys available, skipping test")
|
|
|
|
}
|
|
|
|
|
|
|
|
testLock := memory.NewLock()
|
2019-10-24 00:31:36 +03:00
|
|
|
testStore, err := cfstore.NewStore(
|
|
|
|
cfstore.ApiToken(apiToken),
|
|
|
|
cfstore.AccountID(accountID),
|
|
|
|
cfstore.Namespace(kvID),
|
2019-10-17 18:31:02 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
|
|
testStorage := NewStorage(testLock, testStore)
|
|
|
|
|
|
|
|
conf := cloudflare.NewDefaultConfig()
|
|
|
|
conf.AuthToken = apiToken
|
|
|
|
conf.ZoneToken = apiToken
|
|
|
|
testChallengeProvider, err := cloudflare.NewDNSProviderConfig(conf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
testProvider := New(
|
|
|
|
acme.AcceptToS(true),
|
|
|
|
acme.Cache(testStorage),
|
|
|
|
acme.CA(acme.LetsEncryptStagingCA),
|
|
|
|
acme.ChallengeProvider(testChallengeProvider),
|
|
|
|
acme.OnDemand(false),
|
|
|
|
)
|
|
|
|
|
|
|
|
listener, err := testProvider.NewListener("*.micro.mu", "micro.mu")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
|
|
go http.Serve(listener, http.NotFoundHandler())
|
|
|
|
time.Sleep(10 * time.Minute)
|
|
|
|
}
|