carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/hostops"
11 "github.com/carlosframework/platform/internal/mail"
12 "github.com/carlosframework/platform/internal/release"
13)
14
15// mailGuardPermits seeds an open admission for each app and returns a
16// context holding a live exclusive app-mutation permit for every one of
17// them: the shape a mail admission's context has.
18func mailGuardPermits(t *testing.T, raw release.Store, apps ...hostops.AppScope) context.Context {
19 t.Helper()
20 ctx := t.Context()
21 w := claimTestWriter(t, raw)
22 for i, a := range apps {
23 appID := "id-" + a.Account + "-" + a.App
24 seedClaimAdmission(t, raw, a.Account, a.App, hostops.AppAdmission{V: 1, AppID: appID, Revision: 1, Epoch: 1, State: "open",
25 Registrations: map[string]hostops.Registration{}, Effects: map[string]hostops.Effect{}})
26 op := "mail-op-" + string(rune('a'+i))
27 h, err := w.BeginExclusive(ctx, a.Account, a.App, appID, hostops.AppOperation{ID: op, Kind: hostops.OpAppMutation, AppID: appID, Phase: "issued", Intent: map[string]string{"action": "mail"}}, claimTestOwner)
28 if err != nil {
29 t.Fatal(err)
30 }
31 next, _, err := h.Runner(time.Now).Issue(ctx, hostops.Effect{ID: op, Resource: "mail-gate:test", Action: "mail"})
32 if err != nil {
33 t.Fatal(err)
34 }
35 ctx = next
36 }
37 return ctx
38}
39
40func putMailJSON(t *testing.T, s release.Store, key string, v any) {
41 t.Helper()
42 body, err := json.Marshal(v)
43 if err != nil {
44 t.Fatal(err)
45 }
46 _, tag, _ := s.Get(context.Background(), key)
47 if _, err = s.Put(context.Background(), key, body, tag); err != nil {
48 t.Fatal(err)
49 }
50}
51
52func putMailClaim(t *testing.T, s release.Store, c mail.DomainClaim) {
53 t.Helper()
54 if c.Schema == 0 {
55 c.Schema = 1
56 }
57 if c.CreatedAt.IsZero() {
58 c.CreatedAt = time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC)
59 }
60 putMailJSON(t, s, mail.DomainClaimKey(c.Domain), c)
61}
62
63func scopesEqual(got []hostops.AppScope, want ...hostops.AppScope) bool {
64 if len(got) != len(want) {
65 return false
66 }
67 set := map[hostops.AppScope]bool{}
68 for _, g := range got {
69 set[g] = true
70 }
71 for _, w := range want {
72 if !set[w] {
73 return false
74 }
75 }
76 return true
77}
78
79// The invariant failure this closes: EnsureDomain calls EnsureIdentity after
80// claimDomain and BEFORE the account domain record exists. The identity is
81// bound to the account's create-only global claim, resolved to the claim's
82// app, and is never accepted on a record alone or on a foreign claim.
83func TestMailGuardBindsNewIdentityToDomainClaim(t *testing.T) {
84 raw := release.NewDirStore(t.TempDir())
85 titogo := hostops.AppScope{Account: "bab", App: "titogo"}
86 ctx := mailGuardPermits(t, raw, titogo)
87 g := backendGuard{data: raw}
88 const domain = "titogo.bab.oncarlos.com"
89
90 // No claim and no record: nothing proves the domain.
91 if _, err := g.domainMembers(ctx, domain, false, domainCreate, "eu-west-1"); !errors.Is(err, errBackendUnresolved) {
92 t.Fatalf("unclaimed domain: %v", err)
93 }
94
95 // A record alone (account data) never proves a NEW identity.
96 putMailJSON(t, raw, mail.DomainKey("bab", "loose.example.com"), mail.DomainRecord{Domain: "loose.example.com", Apps: []string{"titogo"}})
97 if _, err := g.domainMembers(ctx, "loose.example.com", false, domainCreate, "eu-west-1"); !errors.Is(err, errBackendUnproven) {
98 t.Fatalf("record without claim admitted a new identity: %v", err)
99 }
100 // ...but still resolves the effects written after the record.
101 if scopes, err := g.domainMembers(ctx, "loose.example.com", false, domainTouch, ""); err != nil || !scopesEqual(scopes, titogo) {
102 t.Fatalf("record members for a bind: %v %v", scopes, err)
103 }
104
105 // The claim exists, the record does not yet: EnsureIdentity resolves.
106 putMailClaim(t, raw, mail.DomainClaim{Domain: domain, Account: "bab", App: "titogo"})
107 scopes, err := g.domainMembers(ctx, domain, false, domainCreate, "eu-west-1")
108 if err != nil || !scopesEqual(scopes, titogo) {
109 t.Fatalf("claimed domain before its record: %v %v", scopes, err)
110 }
111 // Its DKIM, MAIL FROM and DMARC names resolve to the same owner.
112 for _, name := range []string{"tok1._domainkey." + domain, mail.MailFromDomain(domain, "eu-west-1"), "_dmarc." + domain} {
113 if scopes, err := g.domainMembers(ctx, name, true, domainCreate, ""); err != nil || !scopesEqual(scopes, titogo) {
114 t.Fatalf("publish %s: %v %v", name, scopes, err)
115 }
116 }
117
118 // DeleteIdentity needs the claim's own created-in-region stamp.
119 if _, err := g.domainMembers(ctx, domain, false, domainDestroy, "eu-west-1"); !errors.Is(err, errBackendUnproven) {
120 t.Fatalf("delete of an identity the claim never created: %v", err)
121 }
122 putMailClaim(t, raw, mail.DomainClaim{Domain: domain, Account: "bab", App: "titogo", IdentityRegions: []string{"eu-west-1"}})
123 if _, err := g.domainMembers(ctx, domain, false, domainDestroy, "eu-west-1"); err != nil {
124 t.Fatalf("delete of the claim's own identity: %v", err)
125 }
126 if _, err := g.domainMembers(ctx, domain, false, domainDestroy, "us-east-1"); !errors.Is(err, errBackendUnproven) {
127 t.Fatalf("delete in a region the claim never created: %v", err)
128 }
129}
130
131// A claim held by an account with no permit here owns the name, whatever a
132// permitted account's record or a shorter suffix says.
133func TestMailGuardRefusesForeignClaim(t *testing.T) {
134 raw := release.NewDirStore(t.TempDir())
135 ctx := mailGuardPermits(t, raw, hostops.AppScope{Account: "bab", App: "shop"})
136 g := backendGuard{data: raw}
137
138 // bab holds a (legacy) record for victim.com, but zed holds the claim.
139 putMailJSON(t, raw, mail.DomainKey("bab", "victim.com"), mail.DomainRecord{Domain: "victim.com", Apps: []string{"shop"}})
140 putMailClaim(t, raw, mail.DomainClaim{Domain: "victim.com", Account: "zed", App: "web", IdentityRegions: []string{"eu-west-1"}})
141 for _, use := range []domainUse{domainTouch, domainCreate, domainDestroy} {
142 if _, err := g.domainMembers(ctx, "victim.com", false, use, "eu-west-1"); !errors.Is(err, errBackendUnproven) {
143 t.Fatalf("use %d on a foreign claim: %v", use, err)
144 }
145 }
146
147 // bab owns example.com; zed claimed sub.example.com. A DNS name under
148 // sub.example.com is zed's, not bab's parent domain's.
149 putMailClaim(t, raw, mail.DomainClaim{Domain: "example.com", Account: "bab", App: "shop"})
150 putMailJSON(t, raw, mail.DomainKey("bab", "example.com"), mail.DomainRecord{Domain: "example.com", Apps: []string{"shop"}})
151 putMailClaim(t, raw, mail.DomainClaim{Domain: "sub.example.com", Account: "zed", App: "web"})
152 if _, err := g.domainMembers(ctx, "tok._domainkey.sub.example.com", true, domainCreate, ""); !errors.Is(err, errBackendUnproven) {
153 t.Fatalf("DNS under another account's claimed subdomain: %v", err)
154 }
155 if scopes, err := g.domainMembers(ctx, "tok._domainkey.example.com", true, domainCreate, ""); err != nil || !scopesEqual(scopes, hostops.AppScope{Account: "bab", App: "shop"}) {
156 t.Fatalf("own domain DNS: %v %v", scopes, err)
157 }
158
159 // No permit at all: nothing resolves.
160 if _, err := g.domainMembers(t.Context(), "example.com", false, domainTouch, ""); !errors.Is(err, errBackendUnresolved) {
161 t.Fatalf("no permits: %v", err)
162 }
163}
164
165// The persisted members (record and claim app) and the proposed member all
166// resolve, so a sibling's re-run needs the first claimer's permit too.
167func TestMailGuardDomainScopeIsComplete(t *testing.T) {
168 raw := release.NewDirStore(t.TempDir())
169 first := hostops.AppScope{Account: "bab", App: "first"}
170 second := hostops.AppScope{Account: "bab", App: "second"}
171 ctx := mailGuardPermits(t, raw, first, second)
172 g := backendGuard{data: raw}
173 putMailClaim(t, raw, mail.DomainClaim{Domain: "shared.com", Account: "bab", App: "first"})
174 putMailJSON(t, raw, mail.DomainKey("bab", "shared.com"), mail.DomainRecord{Domain: "shared.com", Apps: []string{"second"}})
175 scopes, err := g.domainMembers(ctx, "shared.com", false, domainCreate, "eu-west-1")
176 if err != nil || !scopesEqual(scopes, first, second) {
177 t.Fatalf("complete scope: %v %v", scopes, err)
178 }
179}
180
181// Rev 5 P2: main passes "" to SetIdentityConfigSet to unbind an identity when
182// SNS is disabled. The wrapper authorizes the domain and lets the empty
183// destination through; a named set still needs the account's own set name,
184// and an unproven domain still refuses an unbind. Fails on the original
185// wrapper, which resolved "" through ConfigSetName and always refused.
186func TestClaimMailProvisionerSetIdentityConfigSetUnbinds(t *testing.T) {
187 raw := release.NewDirStore(t.TempDir())
188 titogo := hostops.AppScope{Account: "bab", App: "titogo"}
189 ctx := mailGuardPermits(t, raw, titogo)
190 w := claimTestWriter(t, raw)
191 lease, err := w.AcquireMailGate(ctx, hostops.MailGateOwner{Console: "c", Enrollment: "e", Controller: "k"}, hostops.MailGateScope{})
192 if err != nil {
193 t.Fatal(err)
194 }
195 ctx = hostops.WithMailGateLease(ctx, lease)
196
197 const domain = "titogo.bab.oncarlos.com"
198 putMailClaim(t, raw, mail.DomainClaim{Domain: domain, Account: "bab", App: "titogo"})
199 putMailJSON(t, raw, mail.DomainKey("bab", domain), mail.DomainRecord{Domain: domain, Apps: []string{"titogo"}})
200
201 mem := mail.NewMemProvisioner("1")
202 if _, err = mem.EnsureIdentity(ctx, "eu-west-1", domain, "", false); err != nil {
203 t.Fatal(err)
204 }
205 p := newClaimMailProvisioner(mem, backendGuard{data: raw, control: w})
206 set := func() string { return mem.Identities["eu-west-1|"+domain].ConfigSet }
207
208 if err = p.SetIdentityConfigSet(ctx, "eu-west-1", domain, mail.ConfigSetName("bab")); err != nil || set() != mail.ConfigSetName("bab") {
209 t.Fatalf("bind to the account's own set: err=%v set=%q", err, set())
210 }
211 if err = p.SetIdentityConfigSet(ctx, "eu-west-1", domain, "carlos-zed"); err == nil || set() != mail.ConfigSetName("bab") {
212 t.Fatalf("bind to another account's set: err=%v set=%q", err, set())
213 }
214 if err = p.SetIdentityConfigSet(ctx, "eu-west-1", domain, ""); err != nil {
215 t.Fatalf("unbind: %v", err)
216 }
217 if set() != "" {
218 t.Fatalf("unbind left set %q", set())
219 }
220
221 // The same call through the generated-wrapper check path.
222 if _, err = mem.EnsureIdentity(ctx, "eu-west-1", "other.example.com", "", false); err != nil {
223 t.Fatal(err)
224 }
225 check := claimBackendCheck(backendGuard{data: raw, control: w})
226 after := func(domain, set string) json.RawMessage {
227 b, _ := json.Marshal(map[string]string{"region": "eu-west-1", "domain": domain, "configSet": set})
228 return b
229 }
230 target := func(domain, set string) hostops.MutationTarget {
231 return hostops.MutationTarget{Surface: "mail.Provisioner", Method: "SetIdentityConfigSet", Key: "eu-west-1", After: after(domain, set)}
232 }
233 if err = check(ctx, target(domain, "")); err != nil {
234 t.Fatalf("check of an unbind: %v", err)
235 }
236 if err = check(ctx, target("other.example.com", "")); err == nil {
237 t.Fatal("an unbind on a domain nothing proves was authorized")
238 }
239 if err = p.SetIdentityConfigSet(ctx, "eu-west-1", "other.example.com", ""); err == nil {
240 t.Fatal("wrapper unbound a domain nothing proves")
241 }
242}
243
244// Standalone and sender principals resolve by exact forward rendering only.
245func TestMailGuardUserNamesForwardOnly(t *testing.T) {
246 raw := release.NewDirStore(t.TempDir())
247 shop := hostops.AppScope{Account: "bab", App: "shop"}
248 ctx := mailGuardPermits(t, raw, shop)
249 g := backendGuard{data: raw}
250
251 // CreateStandalone's EnsureUser runs before its index is written.
252 user := mail.StandaloneUserName("bab", "shop", "laptop")
253 if scopes, err := g.resolveMailUser(ctx, user); err != nil || !scopesEqual(scopes, shop) {
254 t.Fatalf("standalone of a permitted app: %v %v", scopes, err)
255 }
256 if scopes, err := g.resolveMailUser(ctx, mail.UserName("bab", "shop")); err != nil || !scopesEqual(scopes, shop) {
257 t.Fatalf("delivered user: %v %v", scopes, err)
258 }
259 // Hyphen collision: app shop-eu's names are never shop's.
260 for _, u := range []string{
261 mail.UserName("bab", "shop-eu"),
262 mail.StandaloneUserName("bab", "shop-eu", "laptop"),
263 "carlos-mail-bab-shop+s+",
264 "carlos-mail-bab-shop+s+Bad",
265 "carlos-mail-bab-shop+s+a+s+b",
266 mail.StandaloneUserName("zed", "shop", "laptop"),
267 } {
268 if _, err := g.resolveMailUser(ctx, u); !errors.Is(err, errBackendUnproven) {
269 t.Fatalf("%s resolved: %v", u, err)
270 }
271 }
272
273 // An index naming bab/shop for a name outside bab/shop's namespace is
274 // refused, so an index cannot move another app's principal.
275 victim := mail.UserName("bab", "shop-eu")
276 putMailJSON(t, raw, mail.CallerKey(victim), mail.CallerIndex{Schema: 1, IAMUser: victim, Account: "bab", App: "shop", Sender: mail.SenderAppID})
277 if _, err := g.resolveMailUser(ctx, victim); !errors.Is(err, errBackendUnproven) {
278 t.Fatalf("forged caller index: %v", err)
279 }
280 // A correct index resolves without any permit in ctx.
281 own := mail.StandaloneUserName("bab", "shop", "ci")
282 putMailJSON(t, raw, mail.CallerKey(own), mail.CallerIndex{Schema: 1, IAMUser: own, Account: "bab", App: "shop", Sender: mail.SenderAppID})
283 if scopes, err := g.resolveMailUser(t.Context(), own); err != nil || !scopesEqual(scopes, shop) {
284 t.Fatalf("indexed standalone: %v %v", scopes, err)
285 }
286}
287