forked from TrueCloudLab/lego
42941ccea6
- Packages - Isolate code used by the CLI into the package `cmd` - (experimental) Add e2e tests for HTTP01, TLS-ALPN-01 and DNS-01, use [Pebble](https://github.com/letsencrypt/pebble) and [challtestsrv](https://github.com/letsencrypt/boulder/tree/master/test/challtestsrv) - Support non-ascii domain name (punnycode) - Check all challenges in a predictable order - No more global exported variables - Archive revoked certificates - Fixes revocation for subdomains and non-ascii domains - Disable pending authorizations - use pointer for RemoteError/ProblemDetails - Poll authz URL instead of challenge URL - The ability for a DNS provider to solve the challenge sequentially - Check all nameservers in a predictable order - Option to disable the complete propagation Requirement - CLI, support for renew with CSR - CLI, add SAN on renew - Add command to list certificates. - Logs every iteration of waiting for the propagation - update DNSimple client - update github.com/miekg/dns
278 lines
7.8 KiB
Go
278 lines
7.8 KiB
Go
package acmedns
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/cpu/goacmedns"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
// errorClientErr is used by the Client mocks that return an error.
|
|
errorClientErr = errors.New("errorClient always errors")
|
|
// errorStorageErr is used by the Storage mocks that return an error.
|
|
errorStorageErr = errors.New("errorStorage always errors")
|
|
)
|
|
|
|
const (
|
|
// Fixed test data for unit tests.
|
|
egDomain = "threeletter.agency"
|
|
egFQDN = "_acme-challenge." + egDomain + "."
|
|
egKeyAuth = "⚷"
|
|
)
|
|
|
|
var egTestAccount = goacmedns.Account{
|
|
FullDomain: "acme-dns." + egDomain,
|
|
SubDomain: "random-looking-junk." + egDomain,
|
|
Username: "spooky.mulder",
|
|
Password: "trustno1",
|
|
}
|
|
|
|
// mockClient is a mock implementing the acmeDNSClient interface that always
|
|
// returns a fixed goacmedns.Account from calls to Register.
|
|
type mockClient struct {
|
|
mockAccount goacmedns.Account
|
|
}
|
|
|
|
// UpdateTXTRecord does nothing.
|
|
func (c mockClient) UpdateTXTRecord(_ goacmedns.Account, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
// RegisterAccount returns c.mockAccount and no errors.
|
|
func (c mockClient) RegisterAccount(_ []string) (goacmedns.Account, error) {
|
|
return c.mockAccount, nil
|
|
}
|
|
|
|
// mockUpdateClient is a mock implementing the acmeDNSClient interface that
|
|
// tracks the calls to UpdateTXTRecord in the records map.
|
|
type mockUpdateClient struct {
|
|
mockClient
|
|
records map[goacmedns.Account]string
|
|
}
|
|
|
|
// UpdateTXTRecord saves a record value to c.records for the given acct.
|
|
func (c mockUpdateClient) UpdateTXTRecord(acct goacmedns.Account, value string) error {
|
|
c.records[acct] = value
|
|
return nil
|
|
}
|
|
|
|
// errorRegisterClient is a mock implementing the acmeDNSClient interface that always
|
|
// returns errors from errorUpdateClient.
|
|
type errorUpdateClient struct {
|
|
mockClient
|
|
}
|
|
|
|
// UpdateTXTRecord always returns an error.
|
|
func (c errorUpdateClient) UpdateTXTRecord(_ goacmedns.Account, _ string) error {
|
|
return errorClientErr
|
|
}
|
|
|
|
// errorRegisterClient is a mock implementing the acmeDNSClient interface that always
|
|
// returns errors from RegisterAccount.
|
|
type errorRegisterClient struct {
|
|
mockClient
|
|
}
|
|
|
|
// RegisterAccount always returns an error.
|
|
func (c errorRegisterClient) RegisterAccount(_ []string) (goacmedns.Account, error) {
|
|
return goacmedns.Account{}, errorClientErr
|
|
}
|
|
|
|
// mockStorage is a mock implementing the goacmedns.Storage interface that
|
|
// returns static account data and ignores Save.
|
|
type mockStorage struct {
|
|
accounts map[string]goacmedns.Account
|
|
}
|
|
|
|
// Save does nothing.
|
|
func (m mockStorage) Save() error {
|
|
return nil
|
|
}
|
|
|
|
// Put stores an account for the given domain in m.accounts.
|
|
func (m mockStorage) Put(domain string, acct goacmedns.Account) error {
|
|
m.accounts[domain] = acct
|
|
return nil
|
|
}
|
|
|
|
// Fetch retrieves an account for the given domain from m.accounts or returns
|
|
// goacmedns.ErrDomainNotFound.
|
|
func (m mockStorage) Fetch(domain string) (goacmedns.Account, error) {
|
|
if acct, ok := m.accounts[domain]; ok {
|
|
return acct, nil
|
|
}
|
|
return goacmedns.Account{}, goacmedns.ErrDomainNotFound
|
|
}
|
|
|
|
// errorPutStorage is a mock implementing the goacmedns.Storage interface that
|
|
// always returns errors from Put.
|
|
type errorPutStorage struct {
|
|
mockStorage
|
|
}
|
|
|
|
// Put always errors.
|
|
func (e errorPutStorage) Put(_ string, _ goacmedns.Account) error {
|
|
return errorStorageErr
|
|
}
|
|
|
|
// errorSaveStorage is a mock implementing the goacmedns.Storage interface that
|
|
// always returns errors from Save.
|
|
type errorSaveStorage struct {
|
|
mockStorage
|
|
}
|
|
|
|
// Save always errors.
|
|
func (e errorSaveStorage) Save() error {
|
|
return errorStorageErr
|
|
}
|
|
|
|
// errorFetchStorage is a mock implementing the goacmedns.Storage interface that
|
|
// always returns errors from Fetch.
|
|
type errorFetchStorage struct {
|
|
mockStorage
|
|
}
|
|
|
|
// Fetch always errors.
|
|
func (e errorFetchStorage) Fetch(_ string) (goacmedns.Account, error) {
|
|
return goacmedns.Account{}, errorStorageErr
|
|
}
|
|
|
|
// TestPresent tests that the ACME-DNS Present function for updating a DNS-01
|
|
// challenge response TXT record works as expected.
|
|
func TestPresent(t *testing.T) {
|
|
// validAccountStorage is a mockStorage configured to return the egTestAccount.
|
|
validAccountStorage := mockStorage{
|
|
map[string]goacmedns.Account{
|
|
egDomain: egTestAccount,
|
|
},
|
|
}
|
|
// validUpdateClient is a mockClient configured with the egTestAccount that will
|
|
// track TXT updates in a map.
|
|
validUpdateClient := mockUpdateClient{
|
|
mockClient{egTestAccount},
|
|
make(map[goacmedns.Account]string),
|
|
}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Client acmeDNSClient
|
|
Storage goacmedns.Storage
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "present when client storage returns unexpected error",
|
|
Client: mockClient{egTestAccount},
|
|
Storage: errorFetchStorage{},
|
|
ExpectedError: errorStorageErr,
|
|
},
|
|
{
|
|
Name: "present when client storage returns ErrDomainNotFound",
|
|
Client: mockClient{egTestAccount},
|
|
ExpectedError: ErrCNAMERequired{
|
|
Domain: egDomain,
|
|
FQDN: egFQDN,
|
|
Target: egTestAccount.FullDomain,
|
|
},
|
|
},
|
|
{
|
|
Name: "present when client UpdateTXTRecord returns unexpected error",
|
|
Client: errorUpdateClient{},
|
|
Storage: validAccountStorage,
|
|
ExpectedError: errorClientErr,
|
|
},
|
|
{
|
|
Name: "present when everything works",
|
|
Storage: validAccountStorage,
|
|
Client: validUpdateClient,
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
dp, err := NewDNSProviderClient(test.Client, mockStorage{make(map[string]goacmedns.Account)})
|
|
require.NoError(t, err)
|
|
|
|
// override the storage mock if required by the test case.
|
|
if test.Storage != nil {
|
|
dp.storage = test.Storage
|
|
}
|
|
|
|
// call Present. The token argument can be garbage because the ACME-DNS
|
|
// provider does not use it.
|
|
err = dp.Present(egDomain, "foo", egKeyAuth)
|
|
if test.ExpectedError != nil {
|
|
assert.Equal(t, test.ExpectedError, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Check that the success test case set a record.
|
|
assert.Len(t, validUpdateClient.records, 1)
|
|
|
|
// Check that the success test case set the right record for the right account.
|
|
assert.Len(t, validUpdateClient.records[egTestAccount], 43)
|
|
}
|
|
|
|
// TestRegister tests that the ACME-DNS register function works correctly.
|
|
func TestRegister(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
Client acmeDNSClient
|
|
Storage goacmedns.Storage
|
|
Domain string
|
|
FQDN string
|
|
ExpectedError error
|
|
}{
|
|
{
|
|
Name: "register when acme-dns client returns an error",
|
|
Client: errorRegisterClient{},
|
|
ExpectedError: errorClientErr,
|
|
},
|
|
{
|
|
Name: "register when acme-dns storage put returns an error",
|
|
Client: mockClient{egTestAccount},
|
|
Storage: errorPutStorage{mockStorage{make(map[string]goacmedns.Account)}},
|
|
ExpectedError: errorStorageErr,
|
|
},
|
|
{
|
|
Name: "register when acme-dns storage save returns an error",
|
|
Client: mockClient{egTestAccount},
|
|
Storage: errorSaveStorage{mockStorage{make(map[string]goacmedns.Account)}},
|
|
ExpectedError: errorStorageErr,
|
|
},
|
|
{
|
|
Name: "register when everything works",
|
|
Client: mockClient{egTestAccount},
|
|
ExpectedError: ErrCNAMERequired{
|
|
Domain: egDomain,
|
|
FQDN: egFQDN,
|
|
Target: egTestAccount.FullDomain,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
dp, err := NewDNSProviderClient(test.Client, mockStorage{make(map[string]goacmedns.Account)})
|
|
require.NoError(t, err)
|
|
|
|
// override the storage mock if required by the testcase.
|
|
if test.Storage != nil {
|
|
dp.storage = test.Storage
|
|
}
|
|
|
|
// Call register for the example domain/fqdn.
|
|
err = dp.register(egDomain, egFQDN)
|
|
if test.ExpectedError != nil {
|
|
assert.Equal(t, test.ExpectedError, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|