carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1// Task 17a: the cross-account domain claim. Closes the hole review found in
2// Task 17 — SES identities are per-AWS-ACCOUNT, not per-tenant, so any
3// domain one CARLOS account verifies is verified globally. Before this,
4// EnsureDomain computed §4's IAM policy from a domain set that was never
5// checked for OWNERSHIP: a second account could call EnsureDomain on
6// "victim.com", get the same identity ARN with no proof of control, and
7// Enable would hand it a policy granting ses:SendRawEmail with
8// ses:FromAddress *@victim.com. These tests pin the fix: a create-only,
9// globally-keyed claim decided BEFORE EnsureIdentity ever runs.
10package mail
11
12import (
13 "context"
14 "errors"
15 "strings"
16 "testing"
17 "time"
18
19 "github.com/carlosframework/platform/internal/dnsrec"
20 "github.com/carlosframework/platform/internal/release"
21)
22
23// A second account claiming a domain the first already holds must be
24// refused outright: no identity, no domain record. This is the vulnerable
25// path from Task 17's review, closed.
26//
27// ONE SHARED MemProvisioner for both accounts (M2, review follow-up) — this
28// is the honest model, not two. SES identities are per-AWS-ACCOUNT, and a
29// CARLOS deployment has exactly ONE underlying AWS account behind it,
30// shared by every CARLOS tenant account. Two separate MemProvisioner
31// instances are the one setup that could NOT reproduce the actual bug: each
32// would let account B mint its OWN identity object in its OWN fake, which
33// looks like a failure caught (B "got an identity") but proves nothing
34// about the real defect — in production B would get back the SAME ARN A
35// already holds, immediately able to send as A's domain with no DNS
36// control of its own. A single shared provisioner is the only shape where
37// "B got no identity" actually means what it needs to mean.
38func TestEnsureDomainRefusesASecondAccountsClaim(t *testing.T) {
39 ctx := context.Background()
40 s, w := release.NewDirStore(t.TempDir()), dnsrec.NewMemWriter()
41 p := NewMemProvisioner("shared-aws-account")
42 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
43 d := testDeployment()
44
45 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "victimapp", "victim.com", now); err != nil {
46 t.Fatalf("account A's first claim: %v", err)
47 }
48
49 _, _, err := EnsureDomain(ctx, s, p, w, d, "bbb", "evilapp", "victim.com", now)
50 if err == nil {
51 t.Fatal("account B was allowed to claim a domain account A already holds")
52 }
53 if len(p.Identities) != 1 {
54 t.Fatalf("identities in the shared AWS account = %d, want 1 (A's only) — B must never have reached EnsureIdentity", len(p.Identities))
55 }
56 if rec, _, err := GetDomain(ctx, s, "bbb", "victim.com"); err != nil {
57 t.Fatalf("GetDomain: %v", err)
58 } else if rec != nil {
59 t.Fatalf("account B got a domain record for a domain it does not own: %+v", rec)
60 }
61}
62
63// The refusal must be a cross-account information leak of exactly nothing:
64// a losing caller must not learn WHO holds the domain by reading the error.
65func TestEnsureDomainRefusalDoesNotNameTheHolder(t *testing.T) {
66 ctx := context.Background()
67 s, w := release.NewDirStore(t.TempDir()), dnsrec.NewMemWriter()
68 pA, pB := NewMemProvisioner("A"), NewMemProvisioner("B")
69 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
70 d := testDeployment()
71
72 if _, _, err := EnsureDomain(ctx, s, pA, w, d, "secretacct", "victimapp", "victim.com", now); err != nil {
73 t.Fatalf("account A's first claim: %v", err)
74 }
75 _, _, err := EnsureDomain(ctx, s, pB, w, d, "bbb", "evilapp", "victim.com", now)
76 if err == nil {
77 t.Fatal("expected a refusal")
78 }
79 if strings.Contains(err.Error(), "secretacct") {
80 t.Fatalf("refusal named the holding account: %v", err)
81 }
82 if !errors.Is(err, ErrDomainClaimed) {
83 t.Fatalf("refusal was not ErrDomainClaimed: %v", err)
84 }
85}
86
87// The owning account re-running EnsureDomain on its own claimed domain is
88// ordinary idempotent success — a claim must never block its own holder.
89func TestEnsureDomainOwnAccountReRunIsIdempotent(t *testing.T) {
90 ctx := context.Background()
91 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("A"), dnsrec.NewMemWriter()
92 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
93 d := testDeployment()
94
95 first, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "myapp", "mine.com", now)
96 if err != nil {
97 t.Fatalf("first: %v", err)
98 }
99 second, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "myapp", "mine.com", now)
100 if err != nil {
101 t.Fatalf("second: %v", err)
102 }
103 f, sec := first.Regions["eu-west-1"], second.Regions["eu-west-1"]
104 if f.IdentityARN != sec.IdentityARN || f.DKIMTokens[0] != sec.DKIMTokens[0] {
105 t.Fatal("re-running under the SAME account changed identity/tokens")
106 }
107 if len(p.Identities) != 1 {
108 t.Fatalf("identities = %d, want 1", len(p.Identities))
109 }
110}
111
112// Teardown of the last app referencing a domain must release the global
113// claim, and only then — a sibling app still using the domain must keep it
114// held. Once released, another account can claim it.
115func TestTeardownReleasesClaimOnlyWhenLastAppGoes(t *testing.T) {
116 ctx := context.Background()
117 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("A"), dnsrec.NewMemWriter()
118 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
119 d := testDeployment()
120
121 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "app1", "shared.com", now); err != nil {
122 t.Fatalf("app1 ensure: %v", err)
123 }
124 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "app2", "shared.com", now); err != nil {
125 t.Fatalf("app2 ensure: %v", err)
126 }
127
128 // Tear down app1: app2 still references the domain, so the claim must
129 // stay held — a stranger must NOT be able to claim a live domain out
130 // from under a sibling app.
131 if err := Teardown(ctx, s, p, w, d, "aaa", "app1"); err != nil {
132 t.Fatalf("teardown app1: %v", err)
133 }
134 pStranger := NewMemProvisioner("B")
135 if _, _, err := EnsureDomain(ctx, s, pStranger, w, d, "bbb", "strangerapp", "shared.com", now); err == nil {
136 t.Fatal("a stranger claimed a domain still referenced by a sibling app")
137 }
138
139 // Tear down app2, the last referrer: NOW the claim must release.
140 if err := Teardown(ctx, s, p, w, d, "aaa", "app2"); err != nil {
141 t.Fatalf("teardown app2: %v", err)
142 }
143 rec, _, err := EnsureDomain(ctx, s, pStranger, w, d, "bbb", "strangerapp", "shared.com", now)
144 if err != nil {
145 t.Fatalf("stranger should be able to claim the released domain: %v", err)
146 }
147 if rec == nil || len(pStranger.Identities) != 1 {
148 t.Fatal("stranger did not actually get a fresh identity after the claim released")
149 }
150}
151
152// A platform-suffixed domain is not exempt from the claim gate — one code
153// path, no special case. (The separate ownership check below this domain
154// name happens to satisfy — the label matches the caller's own account —
155// is C1's fix and has its own dedicated tests further down.)
156func TestEnsureDomainClaimsAPlatformDomainToo(t *testing.T) {
157 ctx := context.Background()
158 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("A"), dnsrec.NewMemWriter()
159 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
160 d := testDeployment()
161
162 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "harbourmail", "harbourmail.aaa.oncarlos.com", now); err != nil {
163 t.Fatalf("EnsureDomain: %v", err)
164 }
165 claim, err := getDomainClaim(ctx, s, "harbourmail.aaa.oncarlos.com")
166 if err != nil {
167 t.Fatalf("getDomainClaim: %v", err)
168 }
169 if claim == nil || claim.Account != "aaa" {
170 t.Fatalf("platform domain did not get a claim: %+v", claim)
171 }
172}
173
174// The claim must be the FIRST durable side effect — written before
175// EnsureIdentity ever runs. Inject a one-shot failure at EnsureIdentity:
176// the claim must already be there, but no identity was created. A re-run by
177// the SAME account must then succeed — the claim must not block its own
178// owner, even though it was written before the walk that owns it finished.
179func TestEnsureDomainWritesClaimBeforeIdentity(t *testing.T) {
180 ctx := context.Background()
181 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("A"), dnsrec.NewMemWriter()
182 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
183 d := testDeployment()
184
185 p.FailNext["EnsureIdentity"] = errors.New("SES down")
186 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "myapp", "mine.com", now); err == nil {
187 t.Fatal("EnsureDomain succeeded despite an injected EnsureIdentity failure")
188 }
189 claim, err := getDomainClaim(ctx, s, "mine.com")
190 if err != nil {
191 t.Fatalf("getDomainClaim: %v", err)
192 }
193 if claim == nil || claim.Account != "aaa" {
194 t.Fatalf("claim missing after the injected failure: %+v", claim)
195 }
196 if len(p.Identities) != 0 {
197 t.Fatalf("identity was created despite the injected failure: %d", len(p.Identities))
198 }
199
200 rec, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "myapp", "mine.com", now)
201 if err != nil {
202 t.Fatalf("re-run by the claim's own owner must succeed: %v", err)
203 }
204 if rec == nil || len(p.Identities) != 1 {
205 t.Fatal("re-run did not complete provisioning")
206 }
207}
208
209// ---- C1 (review follow-up): platform names need an ownership check too --
210//
211// §4a originally claimed platform-suffixed names ("<app>.<sqid>.oncarlos.com")
212// need no ownership check because they are "account-scoped by construction".
213// That was wrong: DomainKindFor matches only the SUFFIX, and nothing
214// compared the account label to the caller. Worse than the custom-domain
215// case the claim alone protects, because CARLOS itself publishes the DKIM,
216// MAIL FROM and DMARC records — the attacker needs no DNS control at all.
217
218// Account bbb must never obtain a platform name that names a DIFFERENT
219// account's sqid — no identity, no DNS, and (since the check runs before
220// the claim) no claim left behind either, so a refused attempt cannot even
221// squat the name for later.
222func TestEnsureDomainRefusesAPlatformNameForAnotherAccount(t *testing.T) {
223 ctx := context.Background()
224 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("shared"), dnsrec.NewMemWriter()
225 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
226 d := testDeployment()
227
228 _, _, err := EnsureDomain(ctx, s, p, w, d, "bbb", "evilapp", "harbourmail.aaa.oncarlos.com", now)
229 if err == nil {
230 t.Fatal("account bbb was allowed to claim account aaa's platform name")
231 }
232 if !errors.Is(err, ErrNotOwnPlatformName) {
233 t.Fatalf("err = %v, want ErrNotOwnPlatformName", err)
234 }
235 if len(p.Identities) != 0 {
236 t.Fatalf("an SES identity was created for a platform name bbb does not own: %d", len(p.Identities))
237 }
238 if len(w.Records) != 0 {
239 t.Fatalf("DNS was published for a platform name bbb does not own: %d records", len(w.Records))
240 }
241 if claim, err := getDomainClaim(ctx, s, "harbourmail.aaa.oncarlos.com"); err != nil || claim != nil {
242 t.Fatalf("a refused platform-name attempt left a claim behind: %+v", claim)
243 }
244}
245
246// The mirror image: bbb asking for ITS OWN platform name succeeds exactly
247// as before this fix — the ownership check must not become a second way to
248// refuse a legitimate member.
249func TestEnsureDomainAcceptsItsOwnPlatformName(t *testing.T) {
250 ctx := context.Background()
251 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("shared"), dnsrec.NewMemWriter()
252 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
253 d := testDeployment()
254
255 rec, _, err := EnsureDomain(ctx, s, p, w, d, "bbb", "myapp", "harbourmail.bbb.oncarlos.com", now)
256 if err != nil {
257 t.Fatalf("EnsureDomain: %v", err)
258 }
259 if rec.Kind != KindPlatform {
260 t.Fatalf("kind = %q, want platform", rec.Kind)
261 }
262}
263
264// The bare platform suffix names no account label at all, so no account —
265// including a well-formed one — can own it. It was accepted before this
266// fix, and CARLOS would have written a DMARC record at its own zone apex.
267func TestEnsureDomainRefusesTheBarePlatformApex(t *testing.T) {
268 ctx := context.Background()
269 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("shared"), dnsrec.NewMemWriter()
270 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
271 d := testDeployment()
272
273 _, _, err := EnsureDomain(ctx, s, p, w, d, "bbb", "myapp", d.PlatformSuffix, now)
274 if err == nil {
275 t.Fatal("the bare platform apex was accepted")
276 }
277 if !errors.Is(err, ErrNotOwnPlatformName) {
278 t.Fatalf("err = %v, want ErrNotOwnPlatformName", err)
279 }
280 if len(w.Records) != 0 {
281 t.Fatalf("DNS was published at the platform's own zone apex: %d records", len(w.Records))
282 }
283}
284
285// A custom domain names no account label — the ownership check must never
286// fire for KindCustom, whatever the caller's account is.
287func TestEnsureDomainCustomDomainUnaffectedByPlatformOwnershipCheck(t *testing.T) {
288 ctx := context.Background()
289 s, p, w := release.NewDirStore(t.TempDir()), NewMemProvisioner("shared"), dnsrec.NewMemWriter()
290 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
291 d := testDeployment()
292
293 rec, _, err := EnsureDomain(ctx, s, p, w, d, "bbb", "myapp", "example.com", now)
294 if err != nil {
295 t.Fatalf("EnsureDomain on a custom domain was refused: %v", err)
296 }
297 if rec.Kind != KindCustom {
298 t.Fatalf("kind = %q, want custom", rec.Kind)
299 }
300}
301
302// ---- I1 (review follow-up): a stranded claim must actually be releasable -
303
304// failDeleteOnce fails the FIRST Delete against one specific key, then
305// behaves normally — the crash point I1's first strand is about: a failure
306// between destroying the shared resource (identity/DNS) and deleting the
307// account's own domain record.
308type failDeleteOnce struct {
309 release.Store
310 key string
311 failed bool
312}
313
314func (f *failDeleteOnce) Delete(ctx context.Context, key string) error {
315 if !f.failed && key == f.key {
316 f.failed = true
317 return errors.New("bucket delete failed")
318 }
319 return f.Store.Delete(ctx, key)
320}
321
322// Strand 1: a failure deleting the account's own domain record must not
323// strand the claim. Before I1's fix (release AFTER delete), a crash here
324// meant the record survived on disk, ListDomains would list it again, but
325// this exact branch had already tried and failed once — WORSE, if the
326// record delete itself never succeeds on retry either, the release call
327// was UNREACHABLE forever, because Teardown's only path to it ran through
328// a record delete that keeps failing. Releasing FIRST (this fix) means the
329// claim is gone immediately, regardless of whether the record delete ever
330// succeeds.
331func TestTeardownClaimReleaseSurvivesAFailedRecordDelete(t *testing.T) {
332 ctx := context.Background()
333 inner := release.NewDirStore(t.TempDir())
334 w := dnsrec.NewMemWriter()
335 p := NewMemProvisioner("A")
336 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
337 d := testDeployment()
338
339 if _, _, err := EnsureDomain(ctx, inner, p, w, d, "aaa", "myapp", "mine.com", now); err != nil {
340 t.Fatalf("ensure: %v", err)
341 }
342
343 s := &failDeleteOnce{Store: inner, key: DomainKey("aaa", "mine.com")}
344 if err := Teardown(ctx, s, p, w, d, "aaa", "myapp"); err == nil {
345 t.Fatal("expected the injected record-delete failure to surface")
346 }
347 if claim, err := getDomainClaim(ctx, inner, "mine.com"); err != nil {
348 t.Fatalf("getDomainClaim: %v", err)
349 } else if claim != nil {
350 t.Fatalf("claim survived a failed record delete: %+v", claim)
351 }
352
353 // A stranger can claim the domain immediately — no retry required.
354 pStranger := NewMemProvisioner("B")
355 if _, _, err := EnsureDomain(ctx, inner, pStranger, w, d, "bbb", "strangerapp", "mine.com", now); err != nil {
356 t.Fatalf("stranger should be able to claim the released domain: %v", err)
357 }
358
359 // Retrying the original Teardown (past the one-shot failure) must still
360 // be self-healing, and must not error trying to release a claim it no
361 // longer holds (it's bbb's now).
362 if err := Teardown(ctx, inner, p, w, d, "aaa", "myapp"); err != nil {
363 t.Fatalf("retry did not repair: %v", err)
364 }
365}
366
367// Strand 2: the far more ORDINARY case — EnsureDomain fails after the claim
368// but before any domain record is ever written (an SES throttle, a tenant
369// limit, anything). Before I1's fix this was a PERMANENT dead end:
370// Teardown's domain loop only visits domains with a record, and this one
371// never got one, so the claim survived every Teardown forever — a global
372// object no product surface could touch, denying the domain to everyone
373// including its rightful owner.
374func TestTeardownReleasesAnOrphanedClaimWithNoDomainRecord(t *testing.T) {
375 ctx := context.Background()
376 s, w := release.NewDirStore(t.TempDir()), dnsrec.NewMemWriter()
377 p := NewMemProvisioner("A")
378 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
379 d := testDeployment()
380
381 p.FailNext["EnsureIdentity"] = errors.New("SES throttled")
382 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "myapp", "mine.com", now); err == nil {
383 t.Fatal("expected the injected failure")
384 }
385 if claim, err := getDomainClaim(ctx, s, "mine.com"); err != nil || claim == nil {
386 t.Fatalf("claim missing before teardown: claim=%+v err=%v", claim, err)
387 }
388 if rec, _, err := GetDomain(ctx, s, "aaa", "mine.com"); err != nil || rec != nil {
389 t.Fatalf("a domain record exists despite the injected failure: %+v", rec)
390 }
391
392 if err := Teardown(ctx, s, p, w, d, "aaa", "myapp"); err != nil {
393 t.Fatalf("teardown: %v", err)
394 }
395 if claim, err := getDomainClaim(ctx, s, "mine.com"); err != nil || claim != nil {
396 t.Fatalf("claim survived teardown despite having no record at all: %+v", claim)
397 }
398
399 pStranger := NewMemProvisioner("B")
400 if _, _, err := EnsureDomain(ctx, s, pStranger, w, d, "bbb", "strangerapp", "mine.com", now); err != nil {
401 t.Fatalf("stranger should now be able to claim the orphaned domain: %v", err)
402 }
403}
404
405// The orphan-reconciliation pass must be scoped to claims with NO record —
406// it must never touch a sibling app's live, fully-recorded domain, even
407// though both belong to the same account being torn down.
408func TestTeardownOrphanReconciliationLeavesRecordedClaimsAlone(t *testing.T) {
409 ctx := context.Background()
410 s, w := release.NewDirStore(t.TempDir()), dnsrec.NewMemWriter()
411 p := NewMemProvisioner("A")
412 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
413 d := testDeployment()
414
415 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "app1", "live.com", now); err != nil {
416 t.Fatalf("app1 ensure: %v", err)
417 }
418 p.FailNext["EnsureIdentity"] = errors.New("SES throttled")
419 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "app2", "orphan.com", now); err == nil {
420 t.Fatal("expected the injected failure")
421 }
422
423 if err := Teardown(ctx, s, p, w, d, "aaa", "app2"); err != nil {
424 t.Fatalf("teardown app2: %v", err)
425 }
426
427 if claim, err := getDomainClaim(ctx, s, "live.com"); err != nil || claim == nil {
428 t.Fatalf("app1's live claim was released by app2's unrelated teardown: claim=%+v err=%v", claim, err)
429 }
430 if rec, _, err := GetDomain(ctx, s, "aaa", "live.com"); err != nil || rec == nil {
431 t.Fatalf("app1's live domain record was disturbed: %+v", rec)
432 }
433 if claim, err := getDomainClaim(ctx, s, "orphan.com"); err != nil || claim != nil {
434 t.Fatalf("app2's orphaned claim survived its own teardown: %+v", claim)
435 }
436}
437
438// ---- Round 2 (review follow-up): the sweep must not over-release, and --
439// ---- must not hand a stranger a live identity -------------------------
440
441// TestTeardownOrphanSweepScopedToTheAttemptingApp (I1 item 1, round 2): an
442// UNRELATED Teardown for a different app in the SAME account must never
443// release another app's in-flight claim. The window this closes is not a
444// microsecond race between goroutines — it is however long the member
445// takes to retry after a throttle (a quota bump, a support ticket: hours),
446// and nothing but the App attribution on the claim tells the sweep
447// "abandoned" from "about to be retried".
448func TestTeardownOrphanSweepScopedToTheAttemptingApp(t *testing.T) {
449 ctx := context.Background()
450 s, w := release.NewDirStore(t.TempDir()), dnsrec.NewMemWriter()
451 p := NewMemProvisioner("shared")
452 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
453 d := testDeployment()
454
455 // app2 is ordinary and unrelated to the domain under test.
456 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "app2", "app2-domain.com", now); err != nil {
457 t.Fatalf("app2 setup: %v", err)
458 }
459
460 // app3 leaves an orphan: claimed, no record — an ordinary retryable
461 // state, not an abandonment.
462 p.FailNext["EnsureIdentity"] = errors.New("SES throttled")
463 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "app3", "inflight.com", now); err == nil {
464 t.Fatal("expected the injected failure")
465 }
466
467 // An UNRELATED Teardown, for a DIFFERENT app in the SAME account, must
468 // not touch app3's in-flight claim.
469 if err := Teardown(ctx, s, p, w, d, "aaa", "app2"); err != nil {
470 t.Fatalf("teardown app2: %v", err)
471 }
472 if claim, err := getDomainClaim(ctx, s, "inflight.com"); err != nil || claim == nil {
473 t.Fatalf("app2's teardown released app3's in-flight claim: claim=%+v err=%v", claim, err)
474 }
475
476 // app3's OWN teardown (a genuine give-up) still releases it.
477 if err := Teardown(ctx, s, p, w, d, "aaa", "app3"); err != nil {
478 t.Fatalf("teardown app3: %v", err)
479 }
480 if claim, err := getDomainClaim(ctx, s, "inflight.com"); err != nil || claim != nil {
481 t.Fatalf("app3's own teardown did not release its orphan: %+v", claim)
482 }
483}
484
485// TestTeardownOrphanSweepDestroysTheIdentityBeforeReleasing (I1 item 2,
486// round 2): if the crash that orphaned a claim landed AFTER EnsureIdentity
487// minted a real SES identity but BEFORE any record was ever written, the
488// sweep must destroy that identity before releasing — otherwise a stranger
489// who claims the domain next inherits the victim's already-minted ARN and
490// DKIM tokens with no DNS proof of their own, exactly what the review
491// demonstrated (stranger inherited ARN=…identity/cust.com,
492// DKIM=[tokeu1 tokeu2 tokeu3] — the victim's own tokens).
493func TestTeardownOrphanSweepDestroysTheIdentityBeforeReleasing(t *testing.T) {
494 ctx := context.Background()
495 s, w := release.NewDirStore(t.TempDir()), dnsrec.NewMemWriter()
496 p := NewMemProvisioner("shared-aws-account")
497 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
498 d := testDeployment() // SNSTopicARN set, so EnsureConfigSet runs right after EnsureIdentity
499
500 // Crash AFTER the identity is minted but BEFORE the record is ever
501 // written: EnsureConfigSet is the walk's very next step after
502 // EnsureIdentity, so failing it here reproduces exactly that ordering.
503 p.FailNext["EnsureConfigSet"] = errors.New("SES config set throttle")
504 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", "victimapp", "cust.com", now); err == nil {
505 t.Fatal("expected the injected failure")
506 }
507 victim, ok := p.Identities[identityKey(d.Region, "cust.com")]
508 if !ok {
509 t.Fatal("test setup: the identity was not actually minted before the injected failure")
510 }
511 victimTokens := append([]string(nil), victim.DKIMTokens...)
512 if rec, _, err := GetDomain(ctx, s, "aaa", "cust.com"); err != nil || rec != nil {
513 t.Fatalf("a domain record exists despite the injected failure: %+v", rec)
514 }
515
516 if err := Teardown(ctx, s, p, w, d, "aaa", "victimapp"); err != nil {
517 t.Fatalf("teardown: %v", err)
518 }
519 if _, ok := p.Identities[identityKey(d.Region, "cust.com")]; ok {
520 t.Fatal("the orphan sweep released the claim without destroying the live identity")
521 }
522
523 // A stranger claiming the released domain must get a FRESH identity —
524 // unverified, with NEW DKIM tokens — never the victim's ARN state or
525 // already-issued tokens.
526 rec, _, err := EnsureDomain(ctx, s, p, w, d, "bbb", "strangerapp", "cust.com", now)
527 if err != nil {
528 t.Fatalf("stranger should be able to claim after the sweep: %v", err)
529 }
530 got := rec.Regions[d.Region]
531 for _, tok := range got.DKIMTokens {
532 for _, old := range victimTokens {
533 if tok == old {
534 t.Fatalf("stranger inherited the victim's DKIM token %q", tok)
535 }
536 }
537 }
538 if got.Status != StatusPending {
539 t.Fatalf("stranger's identity status = %q, want pending (unverified) — a fresh identity, not the victim's verified one", got.Status)
540 }
541}
542
543// ---- C1 coverage gap (item 3, round 2): pin equality, not sub/superset --
544
545// TestPlatformNameOwnedByRequiresExactLabelEquality pins platformNameOwnedBy's
546// comparison as EQUALITY — neither a HasSuffix nor a HasPrefix substitution
547// may pass this table. Also covers the bare-apex early return with an
548// account name ("com") chosen specifically so the FALLBACK label-extraction
549// logic would otherwise (wrongly) accept it — deleting the early-return
550// guard is invisible against every OTHER case in this file, because the
551// fallback happens to compute "com" from the apex and no other test
552// account is named that.
553func TestPlatformNameOwnedByRequiresExactLabelEquality(t *testing.T) {
554 d := testDeployment() // PlatformSuffix = "oncarlos.com"
555 cases := []struct {
556 name string
557 domain string
558 acct string
559 want bool
560 }{
561 {"exact label match", "x.aaa.oncarlos.com", "aaa", true},
562 {"caller is a strict prefix of the real label", "x.aaa.oncarlos.com", "aa", false},
563 {"caller is a strict superset of the real label", "x.aa.oncarlos.com", "aaa", false},
564 {"bare apex refused even for the account named after the fallback's own tail label", d.PlatformSuffix, "com", false},
565 }
566 for _, c := range cases {
567 t.Run(c.name, func(t *testing.T) {
568 if got := platformNameOwnedBy(d, c.domain, c.acct); got != c.want {
569 t.Fatalf("platformNameOwnedBy(%q, %q) = %v, want %v", c.domain, c.acct, got, c.want)
570 }
571 })
572 }
573}
574
575// TestTeardownOrphanSweepDoesNotCrossAccountsOnAMatchingAppName (round 3,
576// review follow-up): the sweep's filter is `c.Account != acct ||
577// c.App != app` — BOTH halves ANDed together, not App alone. App names are
578// short, common words, so two DIFFERENT accounts sharing one (e.g. both
579// running an app called "web") is entirely plausible. If the Account half
580// ever regressed out, the sweep would read GetDomain under the WRONG
581// account, find nothing (domain records are account-keyed), conclude
582// "orphan", and call DeleteIdentity on a domain ANOTHER account has
583// live — destroying it — with releaseDomainClaim's own owner check only
584// then quietly no-op'ing the claim removal itself. The destructive
585// DeleteIdentity call happens BEFORE that guard, so the identity is gone
586// regardless. This test sets up exactly that shape (two accounts, the
587// SAME app name) and asserts account B's Teardown is a complete no-op
588// against account A's orphan: claim intact, identity intact.
589func TestTeardownOrphanSweepDoesNotCrossAccountsOnAMatchingAppName(t *testing.T) {
590 ctx := context.Background()
591 s, w := release.NewDirStore(t.TempDir()), dnsrec.NewMemWriter()
592 p := NewMemProvisioner("shared")
593 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
594 d := testDeployment()
595 const sameApp = "web"
596
597 // Account A leaves an orphan under app "web": claimed, identity
598 // minted, no record — the crash-after-EnsureIdentity shape item 2
599 // (round 2) closed for the SAME account/app; this test is about a
600 // DIFFERENT account reaching it.
601 p.FailNext["EnsureConfigSet"] = errors.New("SES config set throttle")
602 if _, _, err := EnsureDomain(ctx, s, p, w, d, "aaa", sameApp, "orphaned-by-a.com", now); err == nil {
603 t.Fatal("expected the injected failure")
604 }
605 if _, ok := p.Identities[identityKey(d.Region, "orphaned-by-a.com")]; !ok {
606 t.Fatal("test setup: account A's identity was not actually minted")
607 }
608 if rec, _, err := GetDomain(ctx, s, "aaa", "orphaned-by-a.com"); err != nil || rec != nil {
609 t.Fatalf("a domain record exists despite the injected failure: %+v", rec)
610 }
611
612 // Account B, a totally unrelated account, happens to name its own app
613 // "web" too and tears it down. This must be a complete no-op against
614 // account A's orphan.
615 if err := Teardown(ctx, s, p, w, d, "bbb", sameApp); err != nil {
616 t.Fatalf("teardown bbb/%s: %v", sameApp, err)
617 }
618 if claim, err := getDomainClaim(ctx, s, "orphaned-by-a.com"); err != nil || claim == nil || claim.Account != "aaa" {
619 t.Fatalf("account B's teardown disturbed account A's claim: claim=%+v err=%v", claim, err)
620 }
621 if _, ok := p.Identities[identityKey(d.Region, "orphaned-by-a.com")]; !ok {
622 t.Fatal("account B's teardown destroyed account A's LIVE identity on a matching app name")
623 }
624}
625