package edge import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "errors" "math/big" "reflect" "slices" "sort" "strings" "testing" "time" "golang.org/x/crypto/acme/autocert" "github.com/carlosframework/platform/internal/registry" ) // selfSignedWildcard mints a throwaway *.parent cert valid for the given // window — enough for matching/renewal/PEM tests, no ACME involved. func selfSignedWildcard(t *testing.T, parent string, notAfter time.Time) (*tls.Certificate, *ecdsa.PrivateKey) { t.Helper() return selfSignedLeaf(t, "*."+parent, notAfter) } // selfSignedLeaf mints a throwaway cert for one DNS name (a host, or a // "*.parent" wildcard) valid until notAfter. func selfSignedLeaf(t *testing.T, dnsName string, notAfter time.Time) (*tls.Certificate, *ecdsa.PrivateKey) { t.Helper() key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } tmpl := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: dnsName}, DNSNames: []string{dnsName}, NotBefore: time.Now().Add(-time.Hour), NotAfter: notAfter, } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) if err != nil { t.Fatal(err) } leaf, err := x509.ParseCertificate(der) if err != nil { t.Fatal(err) } return &tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key, Leaf: leaf}, key } func TestParentsFor(t *testing.T) { hosts := []string{ "kass.abs.oncarlos.com", // app host → parent abs.oncarlos.com "blog.abs.oncarlos.com", // same parent, deduped "echo.bab.oncarlos.com", // second account "canary.kass.abs.oncarlos.com", // canary depth → no wildcard parent "oncarlos.com", // apex → skipped "console.example.com", // foreign domain → skipped "UPPER.CASE.ONCARLOS.COM", // case-normalized → case.oncarlos.com "x.my-shop.oncarlos.com", // a label no account could hold → no parent (#215) "x.acct3.oncarlos.com", // nor one with a digit in it "x.ops.oncarlos.com", // nor "o": not in the sqid alphabet } got := parentsFor([]string{"go.tito.io"}, "oncarlos.com", hosts) want := []string{"abs.oncarlos.com", "bab.oncarlos.com", "case.oncarlos.com", "go.tito.io"} if !reflect.DeepEqual(got, want) { t.Fatalf("parentsFor = %v, want %v", got, want) } // No apps domain → static only. if got := parentsFor([]string{"go.tito.io"}, "", hosts); !reflect.DeepEqual(got, []string{"go.tito.io"}) { t.Fatalf("static only = %v", got) } // Nothing at all. if got := parentsFor(nil, "", nil); got != nil { t.Fatalf("empty = %v, want nil", got) } } func TestCertForMatchesExactlyOneLabel(t *testing.T) { w := newWildcardManager([]string{"go.tito.io"}, "oncarlos.com", func() []string { return []string{"kass.abs.oncarlos.com"} }, nil, "", autocert.DirCache(t.TempDir()), nil) w.refresh() cert, _ := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(60*24*time.Hour)) w.mu.Lock() w.certs["abs.oncarlos.com"] = cert w.mu.Unlock() cases := []struct { sni string want bool }{ {"kass.abs.oncarlos.com", true}, // exactly one label under parent {"NEW.abs.oncarlos.com.", true}, // never-deployed name, case+dot normalized: instant {"abs.oncarlos.com", false}, // the parent itself (wildcard doesn't cover apex) {"x.y.abs.oncarlos.com", false}, // two labels deep {"kass.bab.oncarlos.com", false}, // parent without a loaded cert (bab not in parents) {"slug.go.tito.io", false}, // static parent present but no cert loaded yet } for _, c := range cases { if got := w.certFor(c.sni) != nil; got != c.want { t.Errorf("certFor(%q) served=%v, want %v", c.sni, got, c.want) } } // An expired wildcard is never served — fall back to per-host issuance. expired, _ := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(-time.Hour)) w.mu.Lock() w.certs["abs.oncarlos.com"] = expired w.mu.Unlock() if w.certFor("kass.abs.oncarlos.com") != nil { t.Fatal("expired wildcard was served") } } func TestWildcardPEMRoundTripAndRenewDecision(t *testing.T) { cert, key := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(60*24*time.Hour)) raw, err := encodeWildcardPEM(cert, key) if err != nil { t.Fatal(err) } back, err := parseWildcardPEM(raw) if err != nil { t.Fatal(err) } if !back.Leaf.NotAfter.Equal(cert.Leaf.NotAfter) { t.Fatalf("roundtrip NotAfter = %v, want %v", back.Leaf.NotAfter, cert.Leaf.NotAfter) } if _, err := parseWildcardPEM([]byte("junk")); err == nil { t.Fatal("parseWildcardPEM(junk): want error") } w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", autocert.DirCache(t.TempDir()), nil) if !w.needsObtain("abs.oncarlos.com", time.Now()) { t.Fatal("no cert loaded: needsObtain should be true") } w.mu.Lock() w.certs["abs.oncarlos.com"] = cert w.mu.Unlock() if w.needsObtain("abs.oncarlos.com", time.Now()) { t.Fatal("60 days left: needsObtain should be false") } if !w.needsObtain("abs.oncarlos.com", cert.Leaf.NotAfter.Add(-15*24*time.Hour)) { t.Fatal("15 days left (<30): needsObtain should be true") } } func TestLoadCachedAndRefreshWake(t *testing.T) { dir := t.TempDir() cache := autocert.DirCache(dir) cert, key := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(60*24*time.Hour)) raw, err := encodeWildcardPEM(cert, key) if err != nil { t.Fatal(err) } if err := cache.Put(context.Background(), wildcardPEMKey("abs.oncarlos.com"), raw); err != nil { t.Fatal(err) } w := newWildcardManager(nil, "oncarlos.com", func() []string { return []string{"kass.abs.oncarlos.com"} }, nil, "", cache, nil) if !w.loadCached(context.Background(), "abs.oncarlos.com") { t.Fatal("loadCached: want true for cached parent") } if w.loadCached(context.Background(), "bab.oncarlos.com") { t.Fatal("loadCached: want false for uncached parent") } // refresh derives the parent and, since its cert is already loaded and // fresh, does NOT wake the run loop; a parent with no cert does. w.refresh() select { case <-w.wake: t.Fatal("refresh with fresh cert should not wake") default: } w2 := newWildcardManager(nil, "oncarlos.com", func() []string { return []string{"kass.abs.oncarlos.com"} }, nil, "", autocert.DirCache(t.TempDir()), nil) w2.refresh() select { case <-w2.wake: default: t.Fatal("refresh with missing cert should wake the run loop") } } func TestLoadCachedRejectsWrongCoverage(t *testing.T) { // (a) a cert valid for the WRONG parent, stashed under abs.oncarlos.com's // key — loadCached must refuse it and store nothing. wrongParent, wrongParentKey := selfSignedWildcard(t, "bab.oncarlos.com", time.Now().Add(60*24*time.Hour)) rawWrongParent, err := encodeWildcardPEM(wrongParent, wrongParentKey) if err != nil { t.Fatal(err) } cache := autocert.DirCache(t.TempDir()) if err := cache.Put(context.Background(), wildcardPEMKey("abs.oncarlos.com"), rawWrongParent); err != nil { t.Fatal(err) } w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache, nil) if w.loadCached(context.Background(), "abs.oncarlos.com") { t.Fatal("loadCached: want false for cert covering the wrong parent") } w.mu.Lock() _, stored := w.certs["abs.oncarlos.com"] w.mu.Unlock() if stored { t.Fatal("loadCached: must not store a cert that doesn't cover the parent") } // (b) a cert whose stored key does NOT match the leaf's public key. cert, _ := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(60*24*time.Hour)) otherKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } rawMismatch, err := encodeWildcardPEM(cert, otherKey) if err != nil { t.Fatal(err) } cache2 := autocert.DirCache(t.TempDir()) if err := cache2.Put(context.Background(), wildcardPEMKey("abs.oncarlos.com"), rawMismatch); err != nil { t.Fatal(err) } w2 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache2, nil) if w2.loadCached(context.Background(), "abs.oncarlos.com") { t.Fatal("loadCached: want false for key/leaf mismatch") } w2.mu.Lock() _, stored2 := w2.certs["abs.oncarlos.com"] w2.mu.Unlock() if stored2 { t.Fatal("loadCached: must not store a cert whose key doesn't match its leaf") } } // fakeDNS records present/cleanup calls — the seam obtain talks through. type fakeDNS struct { presented []string // fqdn|value cleaned []string // fqdn failWith error } func (f *fakeDNS) present(_ context.Context, fqdn, value string) (func(context.Context) error, error) { if f.failWith != nil { return nil, f.failWith } f.presented = append(f.presented, fqdn+"|"+value) return func(context.Context) error { f.cleaned = append(f.cleaned, fqdn) return nil }, nil } func TestWildcardAccountKeyStableAcrossManagers(t *testing.T) { dir := t.TempDir() cache := autocert.DirCache(dir) w1 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache, &fakeDNS{}) k1, err := w1.accountKey(context.Background()) if err != nil { t.Fatal(err) } w2 := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", cache, &fakeDNS{}) k2, err := w2.accountKey(context.Background()) if err != nil { t.Fatal(err) } if !k1.Equal(k2) { t.Fatal("account key not stable across managers sharing a cache") } } // A cache backend fault must NOT mint a fresh ACME account — only a genuine // miss does (a fault treated as a miss would register a new account per // hiccup). type faultyCache struct{ autocert.Cache } func (f faultyCache) Get(context.Context, string) ([]byte, error) { return nil, errors.New("backend down") } func TestAccountKeyBackendFaultDoesNotMint(t *testing.T) { w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", faultyCache{autocert.DirCache(t.TempDir())}, &fakeDNS{}) if _, err := w.accountKey(context.Background()); err == nil { t.Fatal("backend fault: want error, got a (freshly minted) key") } } // TestPerParentBackoff exercises the backoff bookkeeping directly (no live // ACME, no spinning run()'s loop): a parent whose nextAttempt is in the // future is skipped, a past nextAttempt clears the skip, and a success // (delete) clears the entry entirely. func TestPerParentBackoff(t *testing.T) { w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, nil, "", autocert.DirCache(t.TempDir()), &fakeDNS{}) if w.backingOff("abs.oncarlos.com", time.Now()) { t.Fatal("no nextAttempt recorded yet: backingOff should be false") } // Simulate the failure path: run() sets nextAttempt an hour out. w.mu.Lock() w.nextAttempt["abs.oncarlos.com"] = time.Now().Add(time.Hour) w.mu.Unlock() if !w.backingOff("abs.oncarlos.com", time.Now()) { t.Fatal("nextAttempt an hour in the future: backingOff should be true") } // Once the window has passed, the same entry no longer backs off. w.mu.Lock() w.nextAttempt["abs.oncarlos.com"] = time.Now().Add(-time.Minute) w.mu.Unlock() if w.backingOff("abs.oncarlos.com", time.Now()) { t.Fatal("nextAttempt in the past: backingOff should be false") } // Simulate the success path: run() deletes the entry outright. w.mu.Lock() w.nextAttempt["abs.oncarlos.com"] = time.Now().Add(time.Hour) delete(w.nextAttempt, "abs.oncarlos.com") w.mu.Unlock() if w.backingOff("abs.oncarlos.com", time.Now()) { t.Fatal("nextAttempt deleted on success: backingOff should be false") } } func TestWrappedGetCertificateFallsThrough(t *testing.T) { w := newWildcardManager(nil, "oncarlos.com", func() []string { return []string{"kass.abs.oncarlos.com"} }, nil, "", autocert.DirCache(t.TempDir()), &fakeDNS{}) w.refresh() cert, _ := selfSignedWildcard(t, "abs.oncarlos.com", time.Now().Add(60*24*time.Hour)) w.mu.Lock() w.certs["abs.oncarlos.com"] = cert w.mu.Unlock() fallbackHit := "" inner := func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { fallbackHit = hello.ServerName return nil, errors.New("inner path") } wrapped := wrapGetCertificate(w, inner) got, err := wrapped(&tls.ClientHelloInfo{ServerName: "new.abs.oncarlos.com"}) if err != nil || got != cert { t.Fatalf("covered name: got (%v, %v), want the wildcard", got, err) } if fallbackHit != "" { t.Fatalf("covered name reached the inner path (%q)", fallbackHit) } if _, err := wrapped(&tls.ClientHelloInfo{ServerName: "custom.example.com"}); err == nil || fallbackHit != "custom.example.com" { t.Fatalf("uncovered name must fall through to inner; fallbackHit=%q err=%v", fallbackHit, err) } // A tls-alpn-01 validation ClientHello for a COVERED name must still // reach autocert's challenge cert, not the wildcard — otherwise that // validation fails every time. fallbackHit = "" if _, err := wrapped(&tls.ClientHelloInfo{ServerName: "new.abs.oncarlos.com", SupportedProtos: []string{"acme-tls/1"}}); err == nil || fallbackHit != "new.abs.oncarlos.com" { t.Fatalf("tls-alpn-01 hello for covered name must reach inner; fallbackHit=%q err=%v", fallbackHit, err) } } func TestNewWildcardFromEnv(t *testing.T) { e := &Edge{} e.upstreams.Store(&map[string]upstream{}) cache := autocert.DirCache(t.TempDir()) t.Setenv("CARLOS_WILDCARD_DOMAINS", "") t.Setenv("PLATFORM_APPS_DOMAIN", "") t.Setenv("CARLOS_DNS_PROVIDER", "") t.Setenv("CARLOS_DNSIMPLE_TOKEN", "") if w := newWildcardFromEnv(e, cache); w != nil { t.Fatal("nothing configured: want nil (per-host only, the historical default)") } // Domains but no provider → off (logged), never a broken half-config. t.Setenv("CARLOS_WILDCARD_DOMAINS", "go.tito.io") if w := newWildcardFromEnv(e, cache); w != nil { t.Fatal("domains without provider: want nil") } t.Setenv("CARLOS_DNSIMPLE_TOKEN", "tok") w := newWildcardFromEnv(e, cache) if w == nil { t.Fatal("configured: want a manager") } if !reflect.DeepEqual(w.staticDomains, []string{"go.tito.io"}) { t.Fatalf("staticDomains = %v", w.staticDomains) } } func TestOnReloadHookFires(t *testing.T) { reg := openReg(t) // edge_test.go's existing registry fixture e, err := New(reg, nil) if err != nil { t.Fatal(err) } fired := 0 e.OnReload(func() { fired++ }) if err := e.Reload(); err != nil { t.Fatal(err) } if fired != 1 { t.Fatalf("hook fired %d times, want 1", fired) } } // TestCurrentHostsPlatformOnly guards the wildcard-safety filter: only // platform-minted routes contribute to currentHosts — console-minted // (Platform==true) or records-provisioned (Provisioned==true) — since // parentsFor derives wildcard parents from that list. A host shaped like // .. that landed in the route table by neither path // must never mint a wildcard parent and burn an ACME issuance for it. // // Provisioned rows joined the set on 2026-08-24: a box holding ONLY // provisioned rows (every multi-region edge) derived no parents and served // no mirrored wildcard. A provisioned custom domain is listed here but // mints nothing — parentsFor's two-labels-under-the-apps-domain rule is the // bound, pinned below. func TestCurrentHostsPlatformOnly(t *testing.T) { e := &Edge{} e.upstreams.Store(&map[string]upstream{ "app.acct.oncarlos.com": {route: registry.Route{Host: "app.acct.oncarlos.com", Platform: true}}, "review.bcd.oncarlos.com": {route: registry.Route{Host: "review.bcd.oncarlos.com", Provisioned: true}}, "story.example.com": {route: registry.Route{Host: "story.example.com", Provisioned: true}}, "custom.example.com": {route: registry.Route{Host: "custom.example.com", Platform: false}}, "evil.acct2.oncarlos.com": {route: registry.Route{Host: "evil.acct2.oncarlos.com", Platform: false}}, }) got := e.currentHosts() sort.Strings(got) want := []string{"app.acct.oncarlos.com", "review.bcd.oncarlos.com", "story.example.com"} if !reflect.DeepEqual(got, want) { t.Fatalf("currentHosts = %v, want %v", got, want) } parents := parentsFor(nil, "oncarlos.com", got) wantParents := []string{"acct.oncarlos.com", "bcd.oncarlos.com"} if !reflect.DeepEqual(parents, wantParents) { t.Fatalf("parents = %v, want %v (a provisioned custom domain mints no parent; the unowned evil row never reaches here)", parents, wantParents) } } func TestDelegatedParentsJoinTheSet(t *testing.T) { w := newWildcardManager(nil, "oncarlos.com", func() []string { return []string{"app.bab.oncarlos.com"} }, func() []string { return []string{"woodstar.app"} }, "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{}) w.refresh() w.mu.Lock() parents := append([]string(nil), w.parents...) w.mu.Unlock() want := []string{"bab.oncarlos.com", "woodstar.app"} if !reflect.DeepEqual(parents, want) { t.Fatalf("parents = %v, want %v", parents, want) } } func TestChallengeFQDNDelegation(t *testing.T) { w := newWildcardManager(nil, "oncarlos.com", func() []string { return nil }, func() []string { return []string{"woodstar.app"} }, "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{}) w.refresh() // A delegated parent's TXT publishes into OUR zone, at the target // the customer's CNAME points to. if got, want := w.challengeFQDN("woodstar.app"), "_acme-challenge.woodstar.app.acme.oncarlos.com"; got != want { t.Errorf("delegated: %q, want %q", got, want) } // A platform-zone parent publishes directly, exactly as before. if got, want := w.challengeFQDN("bab.oncarlos.com"), "_acme-challenge.bab.oncarlos.com"; got != want { t.Errorf("own-zone: %q, want %q", got, want) } } func TestChallengeFQDNWithoutZoneNeverDelegates(t *testing.T) { // No delegation zone configured: even a listed delegated parent // publishes directly (misconfiguration degrades to the historical // behavior, which per-host issuance then covers — never a dead edge). w := newWildcardManager(nil, "", func() []string { return nil }, func() []string { return []string{"woodstar.app"} }, "", autocert.DirCache(t.TempDir()), &fakeDNS{}) w.refresh() if got, want := w.challengeFQDN("woodstar.app"), "_acme-challenge.woodstar.app"; got != want { t.Errorf("%q, want %q", got, want) } } func TestNilDelegatedSourceIsFine(t *testing.T) { // Every existing construction site passes nil — the zero-source // manager must behave exactly as before this feature existed. w := newWildcardManager([]string{"go.tito.io"}, "", func() []string { return nil }, nil, "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{}) w.refresh() w.mu.Lock() parents := append([]string(nil), w.parents...) w.mu.Unlock() if !reflect.DeepEqual(parents, []string{"go.tito.io"}) { t.Fatalf("parents = %v, want [go.tito.io]", parents) } if got, want := w.challengeFQDN("go.tito.io"), "_acme-challenge.go.tito.io"; got != want { t.Errorf("%q, want %q", got, want) } } // TestPlatformParentsWinOverDelegated exercises the precedence rule refresh() // enforces: a delegated entry that collides with a static domain or falls // under the apps domain is dropped before it can hijack a PLATFORM parent's // challenge — that parent's TXT must still publish directly, never into the // delegation zone where no customer CNAME exists. func TestPlatformParentsWinOverDelegated(t *testing.T) { w := newWildcardManager([]string{"go.tito.io"}, "oncarlos.com", func() []string { return []string{"app.bab.oncarlos.com"} }, func() []string { return []string{"go.tito.io", "bab.oncarlos.com", "woodstar.app"} }, "acme.oncarlos.com", autocert.DirCache(t.TempDir()), &fakeDNS{}) w.refresh() // Colliding entries lose: both the static domain and the dynamic // per-account parent publish their challenge directly, exactly as if // they had never appeared in the delegated source. if got, want := w.challengeFQDN("go.tito.io"), "_acme-challenge.go.tito.io"; got != want { t.Errorf("static collision: challengeFQDN = %q, want %q", got, want) } if got, want := w.challengeFQDN("bab.oncarlos.com"), "_acme-challenge.bab.oncarlos.com"; got != want { t.Errorf("apps-domain collision: challengeFQDN = %q, want %q", got, want) } // A genuinely foreign delegated parent still gets the delegated form. if got, want := w.challengeFQDN("woodstar.app"), "_acme-challenge.woodstar.app.acme.oncarlos.com"; got != want { t.Errorf("non-colliding delegated: challengeFQDN = %q, want %q", got, want) } } func TestNewWildcardFromEnvDelegationZone(t *testing.T) { t.Setenv("CARLOS_WILDCARD_DOMAINS", "go.tito.io") t.Setenv("CARLOS_DNS_PROVIDER", "dnsimple") t.Setenv("CARLOS_DNSIMPLE_TOKEN", "test-token") t.Setenv("CARLOS_ACME_DELEGATION_ZONE", " Acme.OnCarlos.Com ") e := &Edge{} w := newWildcardFromEnv(e, autocert.DirCache(t.TempDir())) if w == nil { t.Fatal("manager should be on") } if w.delegationZone != "acme.oncarlos.com" { t.Errorf("delegationZone = %q, want lowercased+trimmed acme.oncarlos.com", w.delegationZone) } } // TestNewWildcardFromEnvInvalidDelegationZoneDegrades covers "validate at // boot, degrade don't die": a malshaped CARLOS_ACME_DELEGATION_ZONE must // never stop the edge from booting wildcards — it just turns delegation off // while per-host and direct (own-zone) DNS-01 keep covering everyone else. func TestNewWildcardFromEnvInvalidDelegationZoneDegrades(t *testing.T) { t.Setenv("CARLOS_WILDCARD_DOMAINS", "go.tito.io") t.Setenv("CARLOS_DNS_PROVIDER", "dnsimple") t.Setenv("CARLOS_DNSIMPLE_TOKEN", "test-token") t.Setenv("CARLOS_ACME_DELEGATION_ZONE", "*.bad zone") e := &Edge{} w := newWildcardFromEnv(e, autocert.DirCache(t.TempDir())) if w == nil { t.Fatal("invalid delegation zone must not stop the edge from booting") } if w.delegationZone != "" { t.Errorf("delegationZone = %q, want empty (delegation off)", w.delegationZone) } } // TestDelegatedSourceReadsRegistry pins Task 4's sub-change 1: newWildcardFromEnv // must wire its delegated source to a LIVE registry read (Task 2's // ListWildcardDomains), not the nil #168 left as a placeholder. Two // wildcard_domains rows, written the way the reconcile pass (Task 3) writes // them, must both surface as delegated parents after refresh(). func TestDelegatedSourceReadsRegistry(t *testing.T) { reg := openReg(t) // edge_test.go's registry fixture for _, wd := range []registry.WildcardDomain{ {Host: "woodstar.app", Account: "acct", App: "app", CreatedAt: 1}, {Host: "example.net", Account: "acct2", App: "app2", CreatedAt: 2}, } { if err := reg.UpsertWildcardDomain(wd); err != nil { t.Fatalf("UpsertWildcardDomain(%s): %v", wd.Host, err) } } e, err := New(reg, nil) if err != nil { t.Fatalf("New: %v", err) } t.Setenv("CARLOS_WILDCARD_DOMAINS", "go.tito.io") t.Setenv("CARLOS_DNS_PROVIDER", "dnsimple") t.Setenv("CARLOS_DNSIMPLE_TOKEN", "test-token") t.Setenv("CARLOS_ACME_DELEGATION_ZONE", "acme.oncarlos.com") w := newWildcardFromEnv(e, autocert.DirCache(t.TempDir())) if w == nil { t.Fatal("manager should be on") } w.refresh() w.mu.Lock() parents := append([]string(nil), w.parents...) w.mu.Unlock() for _, host := range []string{"woodstar.app", "example.net"} { if !slices.Contains(parents, host) { t.Errorf("parents = %v, want %s (from a wildcard_domains registry row)", parents, host) } if !w.isDelegated(host) { t.Errorf("isDelegated(%s) = false, want true", host) } } // IMPORTANT-1 (review fix): a read error after a successful read must // return the LAST-GOOD list, not empty it out — refresh() only runs at // startup and on OnReload (run()'s 12h ticker never re-invokes the // delegated source), so on a quiet box an emptied set would stay empty // until some unrelated write next moved the registry fingerprint, and // certFor would stop serving an already-cached, still-valid wildcard for // every delegated parent in the meantime. if err := reg.Close(); err != nil { t.Fatalf("close registry: %v", err) } got := w.delegated() want := []string{"example.net", "woodstar.app"} slices.Sort(got) if !slices.Equal(got, want) { t.Fatalf("delegated() after a read error = %v, want the last-good list %v", got, want) } } // TestRefreshDropsOverlongChallengeNames pins sub-change 2: a delegated host // whose composed DNS-01 challenge name ("_acme-challenge."+host+"."+zone) // would exceed the DNS 253-byte FQDN limit must never reach parents — an // AuthorizeOrder for it would fail at the CA anyway, and dropping it here // (rather than surfacing that failure through obtain()'s hourly retry loop) // keeps a single pathological claim from producing an endless retry log line // for a name that can never succeed. func TestRefreshDropsOverlongChallengeNames(t *testing.T) { zone := "acme.oncarlos.com" // "_acme-challenge." (16) + longHost (262) + "." + zone (17) > 253. longHost := strings.Repeat("a", 250) + ".example.com" w := newWildcardManager(nil, "", func() []string { return nil }, func() []string { return []string{longHost} }, zone, autocert.DirCache(t.TempDir()), &fakeDNS{}) w.refresh() w.mu.Lock() parents := append([]string(nil), w.parents...) w.mu.Unlock() if slices.Contains(parents, longHost) { t.Fatalf("parents = %v, want %s dropped (composed challenge name exceeds 253 bytes)", parents, longHost) } }