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() } } func newTestConfig(dir string) *Config { c := Config{ Dirs: []string{filepath.Join(dir, "config")}, AccountKeyPath: filepath.Join(dir, "account.key"), Email: "test@example.com", } c.Output.Path = filepath.Join(dir, "certs") return &c } // 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(newTestConfig(dir), 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.configDirs[0], "../certs/example.com/cert.pem") if _, err := os.Stat(p); err != nil { t.Fatalf("file not created: %v", err) } p = filepath.Join(m.configDirs[0], "../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()) } }