carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package domains
2
3// Internal-package tests for reconcile.go's unexported helpers
4// (hostCertExpiry, mintedRoute, materializeAccounts): reconcile_test.go
5// is package domains_test and cannot reach these, so this file follows
6// slugname_test.go's existing convention of a same-package test file living
7// alongside it.
8
9import (
10 "bytes"
11 "context"
12 "crypto/ecdsa"
13 "crypto/elliptic"
14 "crypto/rand"
15 "crypto/x509"
16 "crypto/x509/pkix"
17 "encoding/pem"
18 "errors"
19 "io"
20 "log"
21 "math/big"
22 "net"
23 "os"
24 "path/filepath"
25 "testing"
26 "time"
27
28 "github.com/carlosframework/platform/internal/registry"
29 "github.com/carlosframework/platform/internal/release"
30)
31
32// writeWildcardPEM writes a self-signed cert for name valid until notAfter,
33// in the acme-cache bundle layout certExpiry parses: key block first, then
34// the certificate.
35func writeWildcardPEM(t *testing.T, path, name string, notAfter time.Time) {
36 t.Helper()
37 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
38 if err != nil {
39 t.Fatal(err)
40 }
41 tmpl := &x509.Certificate{
42 SerialNumber: big.NewInt(1),
43 Subject: pkix.Name{CommonName: name},
44 DNSNames: []string{name},
45 NotBefore: time.Now().Add(-time.Hour),
46 NotAfter: notAfter,
47 }
48 der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
49 if err != nil {
50 t.Fatal(err)
51 }
52 keyDER, err := x509.MarshalECPrivateKey(key)
53 if err != nil {
54 t.Fatal(err)
55 }
56 var buf bytes.Buffer
57 if err := pem.Encode(&buf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}); err != nil {
58 t.Fatal(err)
59 }
60 if err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: der}); err != nil {
61 t.Fatal(err)
62 }
63 if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
64 t.Fatal(err)
65 }
66}
67
68// A wildcard-covered host has no per-host acme cache entry — its expiry must
69// come from the covering wildcard-<parent>.pem instead of reporting 0
70// (certless) forever.
71func TestCertExpiryWildcardFallback(t *testing.T) {
72 dir := t.TempDir()
73 // certExpiry parses "key PEM block(s) first, then CERTIFICATE" — build a
74 // minimal self-signed cert the same way the edge's wildcard PEM is laid
75 // out. Reuse/adapt the cert-building helper if reconcile_test.go already
76 // has one; otherwise inline ecdsa+x509 self-sign exactly as in
77 // internal/edge/wildcard_test.go's selfSignedWildcard.
78 notAfter := time.Now().Add(45 * 24 * time.Hour).Truncate(time.Second)
79 writeWildcardPEM(t, filepath.Join(dir, "wildcard-abs.oncarlos.com.pem"), "*.abs.oncarlos.com", notAfter)
80
81 host := "kass.abs.oncarlos.com"
82 if got := hostCertExpiry(dir, host, "oncarlos.com"); got != notAfter.Unix() {
83 t.Fatalf("hostCertExpiry = %d, want %d (wildcard fallback)", got, notAfter.Unix())
84 }
85 // A grandfathered per-host file that expires SOONER than the wildcard
86 // (autocert stopped renewing it once the wildcard took over serving the
87 // host): the wildcard's later expiry must win, not the stale file — the
88 // edge serves the wildcard, so status must not shadow that with a cert
89 // nothing serves anymore.
90 stalePerHost := time.Now().Add(20 * 24 * time.Hour).Truncate(time.Second)
91 writeWildcardPEM(t, filepath.Join(dir, host), host, stalePerHost)
92 if got := hostCertExpiry(dir, host, "oncarlos.com"); got != notAfter.Unix() {
93 t.Fatalf("hostCertExpiry with stale per-host file = %d, want %d (wildcard's later expiry wins)", got, notAfter.Unix())
94 }
95 // A per-host entry that is LATER than the wildcard still wins.
96 perHost := time.Now().Add(80 * 24 * time.Hour).Truncate(time.Second)
97 writeWildcardPEM(t, filepath.Join(dir, host), host, perHost)
98 if got := hostCertExpiry(dir, host, "oncarlos.com"); got != perHost.Unix() {
99 t.Fatalf("hostCertExpiry with per-host file = %d, want %d", got, perHost.Unix())
100 }
101 // Nothing at all → 0, unknown, never a guess.
102 if got := hostCertExpiry(dir, "kass.zzz.oncarlos.com", "oncarlos.com"); got != 0 {
103 t.Fatalf("hostCertExpiry with no files = %d, want 0", got)
104 }
105
106 // A host under a NON-apps-domain wildcard parent (static go.tito.io /
107 // delegated woodstar.app): the fallback must strip one label and try
108 // the parent's wildcard PEM, not require the apps-domain shape.
109 delegatedNotAfter := time.Now().Add(60 * 24 * time.Hour).Truncate(time.Second)
110 writeWildcardPEM(t, filepath.Join(dir, "wildcard-woodstar.app.pem"), "*.woodstar.app", delegatedNotAfter)
111 if got := hostCertExpiry(dir, "paul.woodstar.app", "oncarlos.com"); got != delegatedNotAfter.Unix() {
112 t.Fatalf("delegated-parent fallback = %d, want %d", got, delegatedNotAfter.Unix())
113 }
114 // The apex itself must NOT match its own wildcard.
115 if got := hostCertExpiry(dir, "woodstar.app", "oncarlos.com"); got != 0 {
116 t.Fatalf("apex must not claim the wildcard: got %d, want 0", got)
117 }
118}
119
120// A host whose only cert is the RSA variant (autocert writes <host>+rsa
121// alongside <host> when a client can't do ECDSA) has a cert. Reading it as
122// certless would hold it at "issuing cert" forever.
123func TestHostCertExpiryFindsTheRSAVariant(t *testing.T) {
124 dir := t.TempDir()
125 notAfter := time.Now().Add(45 * 24 * time.Hour).Truncate(time.Second)
126 writeWildcardPEM(t, filepath.Join(dir, "shop.customer.com+rsa"), "shop.customer.com", notAfter)
127 if got := hostCertExpiry(dir, "shop.customer.com", "oncarlos.com"); got != notAfter.Unix() {
128 t.Fatalf("hostCertExpiry = %d, want %d (rsa variant)", got, notAfter.Unix())
129 }
130
131 // The ECDSA per-host file, when it also exists and is LATER, still wins.
132 later := time.Now().Add(80 * 24 * time.Hour).Truncate(time.Second)
133 writeWildcardPEM(t, filepath.Join(dir, "shop.customer.com"), "shop.customer.com", later)
134 if got := hostCertExpiry(dir, "shop.customer.com", "oncarlos.com"); got != later.Unix() {
135 t.Fatalf("hostCertExpiry with later ecdsa file = %d, want %d", got, later.Unix())
136 }
137}
138
139// An authoritative "no such name" is the customer's record not existing yet.
140// A resolver FAILURE is ours. Collapsing both to "" makes the status machine
141// blame the customer for our outage.
142func TestDNSStatusSeparatesNoRecordFromLookupFailure(t *testing.T) {
143 edge := map[string]bool{"1.1.1.1": true}
144
145 notFound := func(string) ([]string, error) {
146 return nil, &net.DNSError{Err: "no such host", IsNotFound: true}
147 }
148 if got := dnsStatus(notFound, "absent.customer.com", edge); got != "" {
149 t.Fatalf("NXDOMAIN = %q, want \"\" (no record yet)", got)
150 }
151
152 servfail := func(string) ([]string, error) {
153 return nil, &net.DNSError{Err: "server misbehaving", IsTemporary: true}
154 }
155 if got := dnsStatus(servfail, "unknown.customer.com", edge); got != "unresolved" {
156 t.Fatalf("resolver failure = %q, want \"unresolved\" — our fault, not the customer's", got)
157 }
158
159 plain := func(string) ([]string, error) {
160 return nil, errors.New("boom")
161 }
162 if got := dnsStatus(plain, "other.customer.com", edge); got != "unresolved" {
163 t.Fatalf("non-DNSError failure = %q, want \"unresolved\"", got)
164 }
165}
166
167// TestMintedRouteCarriesRegionAndDropsSocketWhenFleetPlaced: a minted
168// platform apex must carry its source's Region so the edge dispatches it
169// through the same fleet channel — but when that Region is a fleet
170// placement, the source's Socket is a path on the FLEET BOX, not this
171// platform process, so it must NOT be copied onto the mint (I1, final
172// customer-fleets review).
173func TestMintedRouteCarriesRegionAndDropsSocketWhenFleetPlaced(t *testing.T) {
174 src := registry.Route{
175 Kind: "instance",
176 Socket: "/run/carlos/acme.sock",
177 Region: registry.PlacementTarget("pi"),
178 Status: "ready",
179 App: "acme",
180 Account: "abs",
181 }
182 rt := mintedRoute("acme.abs.oncarlos.com", "acme", "abs", src)
183 if rt.Region != src.Region {
184 t.Fatalf("mintedRoute Region = %q, want %q (fleet-placed source)", rt.Region, src.Region)
185 }
186 if rt.Socket != "" {
187 t.Fatalf("mintedRoute Socket = %q, want empty — a fleet-placed source's socket lives on the fleet box, not here", rt.Socket)
188 }
189
190 // A plain (non-fleet) Region is still copied, and Socket is still copied
191 // too, unchanged from today's behavior — only the fleet-placed case
192 // clears it.
193 src2 := registry.Route{
194 Kind: "instance",
195 Socket: "/run/carlos/acme.sock",
196 Region: "ie",
197 Status: "ready",
198 App: "acme",
199 Account: "abs",
200 }
201 rt2 := mintedRoute("acme.abs.oncarlos.com", "acme", "abs", src2)
202 if rt2.Region != "ie" {
203 t.Fatalf("mintedRoute Region = %q, want %q (plain region)", rt2.Region, "ie")
204 }
205 if rt2.Socket != src2.Socket {
206 t.Fatalf("mintedRoute Socket = %q, want %q — a non-fleet source's socket is still copied", rt2.Socket, src2.Socket)
207 }
208}
209
210// A failed LOCAL row write does not unmake an account. The manifest is the
211// authority on which accounts exist; the registry row is only this box's copy
212// of that fact. known is what makes an apps/<sqid>/<app>/… key path
213// unambiguous, so dropping the sqid on a write failure would make this box
214// read every one of that account's keys as legacy for the rest of the pass —
215// silently skipping its trash markers, route linkage and canary pointers
216// until some later pass happened to get the row written.
217func TestMaterializeAccountsKeepsAnAccountKnownWhenTheRowWriteFails(t *testing.T) {
218 log.SetOutput(io.Discard)
219 t.Cleanup(func() { log.SetOutput(os.Stderr) })
220
221 ctx := context.Background()
222 s := release.NewDirStore(t.TempDir())
223 const id = 793481920157
224 sqid := Sqid(id)
225 body := []byte(`{"id":793481920157,"sqid":"` + sqid + `","name":"Kass","created_at":"2026-08-02T09:00:00Z"}`)
226 if _, err := s.Put(ctx, "accounts/"+sqid+"/account.json", body, ""); err != nil {
227 t.Fatal(err)
228 }
229 acctKeys, err := s.List(ctx, "accounts/")
230 if err != nil {
231 t.Fatal(err)
232 }
233
234 reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db"))
235 if err != nil {
236 t.Fatal(err)
237 }
238 if err := reg.Close(); err != nil { // every write from here on fails
239 t.Fatal(err)
240 }
241 if err := reg.PutAccount(id, sqid, "Kass"); err == nil {
242 t.Fatal("a closed registry accepted PutAccount — this test injects nothing")
243 }
244
245 known := materializeAccounts(ctx, s, acctKeys, reg)
246
247 if !known[sqid] {
248 t.Fatalf("account %s missing from the set: %v — a failed local row write does not unmake an account whose identity already checked out", sqid, known)
249 }
250 // And the consequence that actually matters: the account's app keys still
251 // read as qualified, so the rest of the pass still sees its objects.
252 acct, app, _, ok := appKeyOwner("apps/"+sqid+"/kass/deleted.json", known)
253 if !ok || acct != sqid || app != "kass" {
254 t.Fatalf("appKeyOwner = (%q, %q, %v); want the key read as qualified under %s", acct, app, ok, sqid)
255 }
256}
257