-
ale authored
Add dns-01 support, make the code more readable, add a testing mode that will generate self-signed certificates (for test environments that are not reachable from outside).
ale authoredAdd dns-01 support, make the code more readable, add a testing mode that will generate self-signed certificates (for test environments that are not reachable from outside).
manager_test.go 3.56 KiB
package acmeserver
import (
"context"
"crypto"
"crypto/x509"
"errors"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
)
// A CertGenerator that is just very slow (and will return an error
// in any case).
type slowACME struct{}
func (s *slowACME) GetCertificate(ctx context.Context, _ crypto.Signer, _ *certConfig) ([][]byte, *x509.Certificate, error) {
t := time.After(60 * time.Second)
select {
case <-t:
return nil, nil, errors.New("timed out")
case <-ctx.Done():
return nil, nil, ctx.Err()
}
}
// Create a new test function.
// The first function returned is a cleanup callback.
// The second function returned is the cancel callback.
func newTestManager(t testing.TB, g CertGenerator) (func(), context.CancelFunc, *Manager) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
os.Mkdir(filepath.Join(dir, "config"), 0700)
ioutil.WriteFile(
filepath.Join(dir, "config", "test.yml"),
[]byte("- { names: [example.com] }\n"),
0644,
)
m, err := NewManager(&Config{
Dir: dir,
Email: "test@example.com",
}, g)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
if err := m.Start(ctx); err != nil {
t.Fatal("Start:", err)
}
// Wait just a little bit to give a chance to m.loop() to run.
time.Sleep(50 * time.Millisecond)
return func() {
cancel()
m.Wait()
os.RemoveAll(dir)
}, cancel, m
}
func TestManager_Reload(t *testing.T) {
cleanup, _, m := newTestManager(t, NewSelfSignedCertGenerator())
defer cleanup()
// Data race: we read data owned by another goroutine!
if len(m.certs) < 1 {
t.Fatal("configuration not loaded?")
}
if m.certs[0].cn() != "example.com" {
t.Fatalf("certs[0].cn() is %s, expected example.com", m.certs[0].cn())
}
// Try a reload, catch obvious errors.
m.Reload()
time.Sleep(50 * time.Millisecond)
if len(m.certs) != 1 {
t.Fatalf("certs count is %d, expected 1", len(m.certs))
}
if m.certs[0].cn() != "example.com" {
t.Fatalf("certs[0].cn() is %s, expected example.com", m.certs[0].cn())
}
}
func TestManager_NewCert(t *testing.T) {
cleanup, _, m := newTestManager(t, NewSelfSignedCertGenerator())
defer cleanup()
now := time.Now()
ci := m.certs[0]
if ci.retryDeadline.After(now) {
t.Fatalf("retry deadline is in the future: %v", ci.retryDeadline)
}
testUpdateCh <- true
time.Sleep(100 * time.Millisecond)
// Verify that the retry/renewal timestamp is in the future.
if ci.retryDeadline.Before(now) {
t.Fatalf("retry deadline is in the past after renewal: %v", ci.retryDeadline)
}
// Do we think we have a valid certificate?
if !ci.valid {
t.Fatal("we don't have a valid certificate")
}
// Verify that the credentials have successfully been written
// to storage.
p := filepath.Join(m.configDir, "../certs/example.com/cert.pem")
if _, err := os.Stat(p); err != nil {
t.Fatalf("file not created: %v", err)
}
p = filepath.Join(m.configDir, "../certs/example.com/private_key.pem")
if _, err := os.Stat(p); err != nil {
t.Fatalf("file not created: %v", err)
}
// By triggering a reload now, we should cause the Manager to
// reload the certificate from storage.
m.Reload()
time.Sleep(50 * time.Millisecond)
ci = m.certs[0]
if !ci.valid {
t.Fatal("certificate is invalid after a reload")
}
}
func TestManager_CancelUpdate(t *testing.T) {
start := time.Now()
cleanup, cancel, m := newTestManager(t, &slowACME{})
defer cleanup()
time.Sleep(100 * time.Millisecond)
cancel()
m.Wait()
elapsed := time.Since(start)
if elapsed > 1*time.Second {
t.Fatalf("too much time elapsed (%fs), canceling didn't work?", elapsed.Seconds())
}
}