carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package edge
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
9 "sync/atomic"
10 "testing"
11 "time"
12
13 "golang.org/x/crypto/acme/autocert"
14)
15
16// zoneCheckingDNS is fakeDNS plus a zone check that answers from a fixed
17// set of challenge names it can publish.
18type zoneCheckingDNS struct {
19 fakeDNS
20 writable map[string]bool
21 checked []string
22}
23
24func (z *zoneCheckingDNS) checkZone(_ context.Context, fqdn string) error {
25 z.checked = append(z.checked, fqdn)
26 if z.writable[fqdn] {
27 return nil
28 }
29 return fmt.Errorf("fake: no zone found for %s: %w", fqdn, errNoZone)
30}
31
32// countingCA counts every request a manager makes to its ACME directory.
33func countingCA(t *testing.T) (*httptest.Server, *atomic.Int32) {
34 t.Helper()
35 var n atomic.Int32
36 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37 n.Add(1)
38 http.NotFound(w, r) // a 4xx: the ACME client retries 5xx, and this must fail fast
39 }))
40 t.Cleanup(srv.Close)
41 return srv, &n
42}
43
44// A parent whose challenge zone the provider does not hold never reaches the
45// CA: no account, no order, and the error says why (errNoZone).
46func TestObtainChecksTheZoneBeforeOrdering(t *testing.T) {
47 ca, requests := countingCA(t)
48 dns := &zoneCheckingDNS{}
49 w := newWildcardManager(nil, "carlos.exciting.io", func() []string { return nil },
50 nil, "", autocert.DirCache(t.TempDir()), dns)
51 w.directory = ca.URL
52 err := w.obtain(context.Background(), "bad.carlos.exciting.io")
53 if !errors.Is(err, errNoZone) {
54 t.Fatalf("obtain = %v, want an errNoZone", err)
55 }
56 if got := requests.Load(); got != 0 {
57 t.Fatalf("the CA saw %d request(s) for a parent with no zone, want 0", got)
58 }
59 if len(dns.checked) != 1 || dns.checked[0] != "_acme-challenge.bad.carlos.exciting.io" {
60 t.Fatalf("checked %v, want the parent's own challenge name", dns.checked)
61 }
62 if len(dns.presented) != 0 {
63 t.Fatalf("presented %v, want nothing", dns.presented)
64 }
65}
66
67// A writable zone passes the check and the order goes ahead (here, to a CA
68// that refuses — what matters is that it was asked).
69func TestObtainOrdersWhenTheZoneIsThere(t *testing.T) {
70 ca, requests := countingCA(t)
71 dns := &zoneCheckingDNS{writable: map[string]bool{"_acme-challenge.letsjelly.dev": true}}
72 w := newWildcardManager([]string{"letsjelly.dev"}, "", func() []string { return nil },
73 nil, "", autocert.DirCache(t.TempDir()), dns)
74 w.directory = ca.URL
75 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
76 defer cancel()
77 if err := w.obtain(ctx, "letsjelly.dev"); err == nil || errors.Is(err, errNoZone) {
78 t.Fatalf("obtain = %v, want the CA's refusal", err)
79 }
80 if requests.Load() == 0 {
81 t.Fatal("the CA was never asked for a parent whose zone is writable")
82 }
83}
84
85// A no-zone parent backs off for noZoneRetry rather than the hour a
86// transient failure gets, and the pass asks to run again no sooner.
87func TestPassBacksOffANoZoneParentLonger(t *testing.T) {
88 ca, requests := countingCA(t)
89 w := newWildcardManager(nil, "carlos.exciting.io", func() []string { return nil },
90 nil, "", autocert.DirCache(t.TempDir()), &zoneCheckingDNS{})
91 w.directory = ca.URL
92 w.mu.Lock()
93 w.parents = []string{"bad.carlos.exciting.io"}
94 w.mu.Unlock()
95 start := time.Now()
96 if delay := w.pass(); delay != noZoneRetry {
97 t.Fatalf("pass delay = %s, want %s", delay, noZoneRetry)
98 }
99 w.mu.Lock()
100 next := w.nextAttempt["bad.carlos.exciting.io"]
101 w.mu.Unlock()
102 if next.Before(start.Add(noZoneRetry - time.Minute)) {
103 t.Fatalf("nextAttempt = %s, want about %s from now", next.Sub(start), noZoneRetry)
104 }
105 if requests.Load() != 0 {
106 t.Fatal("the pass reached the CA for a parent with no zone")
107 }
108}
109