carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "bytes"
5 "context"
6 "crypto/ed25519"
7 "encoding/json"
8 "errors"
9 "os"
10 "path/filepath"
11 "runtime"
12 "strings"
13 "sync"
14 "testing"
15 "time"
16
17 "github.com/carlosframework/platform/internal/hostclaim"
18 "github.com/carlosframework/platform/internal/moves"
19 "github.com/carlosframework/platform/internal/provision"
20 "github.com/carlosframework/platform/internal/registry"
21 "github.com/carlosframework/platform/internal/release"
22)
23
24const (
25 hsHost = "held.example.com"
26 hsOp = "mv_abcdefghijklmnopqrstuvwxyz"
27 hsOther = "mv_bcdefghijklmnopqrstuvwxyza"
28 hsBox = "uk-box"
29)
30
31// hsPriv signs the held-stage tests' pointers and promotion records, and
32// hsPub verifies them: the release key a held stage is handed.
33var (
34 hsPriv = ed25519.NewKeyFromSeed(bytes.Repeat([]byte{9}, ed25519.SeedSize))
35 hsPub = hsPriv.Public().(ed25519.PublicKey)
36)
37
38// hsRatchet is a fresh, unarmed pointer digest ratchet: today's admission.
39func hsRatchet(t *testing.T) *release.DigestRatchet {
40 return release.NewDigestRatchet(t.TempDir(), nil, false)
41}
42
43// promoteHeld promotes bab/app's version to canary/x with hsPriv, writing
44// its signed promotion record: a held stage installs only a build §4.1
45// authorizes (settings spec rev 11 §4.3a).
46func promoteHeld(t *testing.T, s release.Store, app, version string) {
47 t.Helper()
48 if err := release.Promote(context.Background(), s, hsPriv, "bab", app, "canary/x", version); err != nil {
49 t.Fatal(err)
50 }
51}
52
53// hsClaim writes host's claim: held by bab/titogo, op as given.
54func hsClaim(t *testing.T, s release.Store, op hostclaim.Op) {
55 t.Helper()
56 created := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC)
57 inc, err := hostclaim.Incarnation("bab", "titogo", hsHost, created)
58 if err != nil {
59 t.Fatal(err)
60 }
61 _, err = hostclaim.Update(context.Background(), s, hostclaim.Observed{Host: hsHost, Absent: true}, func(c *hostclaim.Claim) error {
62 c.Holder = &hostclaim.Holder{Family: "instance", Key: provision.RecordKey("bab", "titogo", hsHost), Account: "bab", App: "titogo",
63 Incarnation: inc, CreatedAt: created, Location: "instances/" + hsHost + "/" + inc, Stamp: 1}
64 c.Op = op
65 c.Touched = hostclaim.Touched{Complete: true, Regions: []string{"ie"}, Boxes: []string{"ie-box"}}
66 return nil
67 })
68 if err != nil {
69 t.Fatal(err)
70 }
71}
72
73func movingOp() hostclaim.Op {
74 return hostclaim.Op{Kind: hostclaim.KindMoving, ID: hsOp, Exec: "ie-box", Participants: []string{"ie-box", hsBox},
75 Intent: map[string]string{"to": "uk", "source": "ie-box", "target": hsBox}}
76}
77
78func hsReport(t *testing.T, s release.Store, op, rel string) {
79 t.Helper()
80 raw, err := json.Marshal(moves.SourceReport{V: 1, Box: "ie-box", MoveID: op, At: time.Now(), Release: rel})
81 if err != nil {
82 t.Fatal(err)
83 }
84 if _, err := s.Put(context.Background(), moves.SourceReportKey(moves.ReportPrefix("bab", "titogo", hsHost, op)), raw, ""); err != nil {
85 t.Fatal(err)
86 }
87}
88
89// hsReportFor is hsReport under another app of the same account.
90func hsReportFor(t *testing.T, s release.Store, app, op, rel string) {
91 t.Helper()
92 raw, err := json.Marshal(moves.SourceReport{V: 1, Box: "ie-box", MoveID: op, At: time.Now(), Release: rel})
93 if err != nil {
94 t.Fatal(err)
95 }
96 if _, err := s.Put(context.Background(), moves.SourceReportKey(moves.ReportPrefix("bab", app, hsHost, op)), raw, ""); err != nil {
97 t.Fatal(err)
98 }
99}
100
101func hsRequest() release.HeldStageRequest {
102 return release.HeldStageRequest{Account: "bab", App: "titogo", Host: hsHost, Version: "v7", Op: hsOp, Box: hsBox}
103}
104
105// TestHeldStageAuthority covers each refused field of heldStageAuthority:
106// the request's op, box and version, the registry row (absent is accepted,
107// held by another op or naming another app is not), the claim (absent,
108// another op, a kind that is not moving or landing, another target,
109// another holder), and the source's report (absent, empty, another
110// release).
111func TestHeldStageAuthority(t *testing.T) {
112 t.Parallel()
113 type fixture struct {
114 reg *registry.Registry
115 s release.Store
116 }
117 good := func(t *testing.T) fixture {
118 s := release.NewDirStore(t.TempDir())
119 hsClaim(t, s, movingOp())
120 hsReport(t, s, hsOp, "v7")
121 return fixture{testRegistry(t), s}
122 }
123 t.Run("a malformed op is refused before any bucket read", func(t *testing.T) {
124 f := good(t)
125 cs := &hsCountingStore{Store: f.s}
126 req := hsRequest()
127 req.Op = "../" + hsOp
128 if err := heldStageAuthority(f.reg, cs, hsBox, t.TempDir())(req, nil); !errors.Is(err, release.ErrStaleHeldStage) {
129 t.Fatalf("authorize: %v, want ErrStaleHeldStage", err)
130 }
131 if cs.n != 0 {
132 t.Fatalf("a malformed op reached the bucket (%d calls)", cs.n)
133 }
134 })
135 t.Run("absent row, moving claim, matching report", func(t *testing.T) {
136 f := good(t)
137 if err := heldStageAuthority(f.reg, f.s, hsBox, t.TempDir())(hsRequest(), nil); err != nil {
138 t.Fatalf("authorize: %v", err)
139 }
140 })
141 t.Run("a row held by the op, and a landing claim", func(t *testing.T) {
142 s := release.NewDirStore(t.TempDir())
143 op := movingOp()
144 op.Kind = hostclaim.KindLanding
145 hsClaim(t, s, op)
146 hsReport(t, s, hsOp, "v7")
147 reg := testRegistry(t)
148 if err := reg.Upsert(registry.Route{Host: hsHost, Kind: "instance", App: "titogo", Account: "bab", Status: "landing", HoldOp: hsOp}); err != nil {
149 t.Fatal(err)
150 }
151 if err := heldStageAuthority(reg, s, hsBox, t.TempDir())(hsRequest(), nil); err != nil {
152 t.Fatalf("authorize: %v", err)
153 }
154 })
155
156 for name, tc := range map[string]struct {
157 box string
158 setup func(*testing.T, fixture, *release.HeldStageRequest)
159 }{
160 "malformed op": {hsBox, func(_ *testing.T, _ fixture, r *release.HeldStageRequest) { r.Op = "../x" }},
161 "another box": {hsBox, func(_ *testing.T, _ fixture, r *release.HeldStageRequest) { r.Box = "ie-box" }},
162 "the claim's target, not this box": {hsBox, func(t *testing.T, f fixture, r *release.HeldStageRequest) {
163 r.Box = "fr-box"
164 rewriteClaim(t, f.s, func(op *hostclaim.Op) { op.Intent["target"] = "fr-box" })
165 }},
166 "claim held by another app": {hsBox, func(t *testing.T, f fixture, r *release.HeldStageRequest) {
167 r.App = "other"
168 hsReportFor(t, f.s, "other", hsOp, "v7")
169 }},
170 "this box is unnamed": {"", func(*testing.T, fixture, *release.HeldStageRequest) {}},
171 "another version": {hsBox, func(_ *testing.T, _ fixture, r *release.HeldStageRequest) { r.Version = "v8" }},
172 "another account": {hsBox, func(_ *testing.T, _ fixture, r *release.HeldStageRequest) { r.Account = "xyz" }},
173 "another app": {hsBox, func(_ *testing.T, _ fixture, r *release.HeldStageRequest) { r.App = "other" }},
174 "an op the claim drops": {hsBox, func(_ *testing.T, _ fixture, r *release.HeldStageRequest) { r.Op = hsOther }},
175 "row held by another op": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
176 if err := f.reg.Upsert(registry.Route{Host: hsHost, Kind: "instance", App: "titogo", Account: "bab", Status: "landing", HoldOp: hsOther}); err != nil {
177 t.Fatal(err)
178 }
179 }},
180 "row not held": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
181 if err := f.reg.Upsert(registry.Route{Host: hsHost, Kind: "instance", App: "titogo", Account: "bab", Status: "ready"}); err != nil {
182 t.Fatal(err)
183 }
184 }},
185 "row of another app": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
186 if err := f.reg.Upsert(registry.Route{Host: hsHost, Kind: "instance", App: "other", Account: "bab", Status: "landing", HoldOp: hsOp}); err != nil {
187 t.Fatal(err)
188 }
189 }},
190 "no claim": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
191 if err := f.s.Delete(context.Background(), "control/hosts/"+hsHost+".json"); err != nil {
192 t.Fatal(err)
193 }
194 }},
195 "claim names another op": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
196 rewriteClaim(t, f.s, func(op *hostclaim.Op) { op.ID = hsOther })
197 }},
198 "claim is deleting": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
199 rewriteClaim(t, f.s, func(op *hostclaim.Op) { op.Kind = hostclaim.KindDeleting })
200 }},
201 "claim targets another box": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
202 rewriteClaim(t, f.s, func(op *hostclaim.Op) { op.Intent["target"] = "fr-box" })
203 }},
204 "no source report": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
205 if err := f.s.Delete(context.Background(), moves.SourceReportKey(moves.ReportPrefix("bab", "titogo", hsHost, hsOp))); err != nil {
206 t.Fatal(err)
207 }
208 }},
209 "report without a release": {hsBox, func(t *testing.T, f fixture, _ *release.HeldStageRequest) {
210 if err := f.s.Delete(context.Background(), moves.SourceReportKey(moves.ReportPrefix("bab", "titogo", hsHost, hsOp))); err != nil {
211 t.Fatal(err)
212 }
213 hsReport(t, f.s, hsOp, "")
214 }},
215 } {
216 t.Run(name, func(t *testing.T) {
217 f := good(t)
218 req := hsRequest()
219 tc.setup(t, f, &req)
220 if err := heldStageAuthority(f.reg, f.s, tc.box, t.TempDir())(req, nil); !errors.Is(err, release.ErrStaleHeldStage) {
221 t.Fatalf("authorize: %v, want ErrStaleHeldStage", err)
222 }
223 })
224 }
225}
226
227// rewriteClaim CASes the host's claim with its op changed by edit.
228func rewriteClaim(t *testing.T, s release.Store, edit func(*hostclaim.Op)) {
229 t.Helper()
230 obs, err := hostclaim.Read(context.Background(), s, hsHost)
231 if err != nil {
232 t.Fatal(err)
233 }
234 if _, err := hostclaim.Update(context.Background(), s, obs, func(c *hostclaim.Claim) error { edit(&c.Op); return nil }); err != nil {
235 t.Fatal(err)
236 }
237}
238
239// hsCountingStore counts every call that reaches the store.
240type hsCountingStore struct {
241 release.Store
242 mu sync.Mutex
243 n int
244}
245
246func (c *hsCountingStore) bump() { c.mu.Lock(); c.n++; c.mu.Unlock() }
247func (c *hsCountingStore) Get(ctx context.Context, key string) ([]byte, string, error) {
248 c.bump()
249 return c.Store.Get(ctx, key)
250}
251func (c *hsCountingStore) List(ctx context.Context, prefix string) ([]string, error) {
252 c.bump()
253 return c.Store.List(ctx, prefix)
254}
255func (c *hsCountingStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) {
256 c.bump()
257 return c.Store.Put(ctx, key, body, ifMatch)
258}
259
260// TestHeldStagePassIsANoOpWithoutRequests is the switch-off guarantee for
261// `carlos adopt`: with no held-stage request on the box (none exist while
262// CARLOS_MOVES_PHASE1 is off, since only a move's target writes one) the
263// pass reads nothing from the bucket and creates nothing on disk.
264func TestHeldStagePassIsANoOpWithoutRequests(t *testing.T) {
265 dd, opt := t.TempDir(), t.TempDir()
266 s := &hsCountingStore{Store: release.NewDirStore(t.TempDir())}
267 heldStagePass(context.Background(), dd, opt, hsPub, hsRatchet(t), testRegistry(t), s, hsBox)
268 if s.n != 0 {
269 t.Fatalf("the pass made %d store calls", s.n)
270 }
271 for _, dir := range []string{dd, opt} {
272 if ents, _ := os.ReadDir(dir); len(ents) != 0 {
273 t.Fatalf("%s gained %v", dir, ents)
274 }
275 }
276}
277
278// TestHeldStagePassServesAnAuthorizedRequest: end to end through the real
279// authority, a request for this box's move stages the source's release.
280func TestHeldStagePassServesAnAuthorizedRequest(t *testing.T) {
281 ctx := context.Background()
282 s := release.NewDirStore(t.TempDir())
283 hsClaim(t, s, movingOp())
284 hsReport(t, s, hsOp, "v7")
285 shipV7(t, s)
286 dd, opt := t.TempDir(), t.TempDir()
287 reqDir := release.HeldStageDir(dd)
288 if err := os.MkdirAll(reqDir, 0o755); err != nil {
289 t.Fatal(err)
290 }
291 raw, _ := json.Marshal(hsRequest())
292 if err := os.WriteFile(filepath.Join(reqDir, hsHost+".json"), raw, 0o644); err != nil {
293 t.Fatal(err)
294 }
295 // A stale sibling: its op is not the claim's.
296 stale := hsRequest()
297 stale.Host, stale.Op = "other.example.com", hsOther
298 raw, _ = json.Marshal(stale)
299 if err := os.WriteFile(filepath.Join(reqDir, stale.Host+".json"), raw, 0o644); err != nil {
300 t.Fatal(err)
301 }
302 heldStagePass(ctx, dd, opt, hsPub, hsRatchet(t), testRegistry(t), s, hsBox)
303 if got, err := release.LiveVersion(opt, hsHost); err != nil || got != "v7" {
304 t.Fatalf("LiveVersion = %q, %v", got, err)
305 }
306 if got, _ := release.LiveVersion(opt, stale.Host); got != "" {
307 t.Fatalf("the stale request was served: %q", got)
308 }
309 if ents, _ := os.ReadDir(reqDir); len(ents) != 0 {
310 names := []string{}
311 for _, e := range ents {
312 names = append(names, e.Name())
313 }
314 t.Fatalf("requests left behind: %s", strings.Join(names, ", "))
315 }
316}
317
318// shipV7 ships bab/titogo v7 for this platform and promotes it to
319// canary/x, so a held stage of it is authorized.
320func shipV7(t *testing.T, s release.Store) {
321 t.Helper()
322 plat := runtime.GOOS + "-" + runtime.GOARCH
323 m := release.Manifest{App: "titogo", Version: "v7", Kind: "binary", Artifacts: map[string]release.Artifact{plat: {}}}
324 if err := release.Ship(context.Background(), s, "bab", m, map[string][]byte{plat: []byte("pinned")}); err != nil {
325 t.Fatal(err)
326 }
327 promoteHeld(t, s, "titogo", "v7")
328}
329
330// TestHeldPublicationIsGuardedByTheAuthority: the authority that admitted a
331// request before staging is re-run around the publication. If the claim
332// moved to another op, or the registry hold was released, while the release
333// was staging, publication returns ErrStaleHeldStage and live/<host> is
334// never written. Unchanged, the publication happens inside the guard.
335func TestHeldPublicationIsGuardedByTheAuthority(t *testing.T) {
336 t.Parallel()
337 heldRow := registry.Route{Host: hsHost, Kind: "instance", App: "titogo", Account: "bab", Status: "landing", HoldOp: hsOp}
338 for name, tc := range map[string]struct {
339 row bool
340 during func(*testing.T, *registry.Registry, release.Store)
341 stale bool
342 }{
343 "unchanged, no row yet": {false, func(*testing.T, *registry.Registry, release.Store) {}, false},
344 "unchanged, held row": {true, func(*testing.T, *registry.Registry, release.Store) {}, false},
345 "the claim moves to another op": {false, func(t *testing.T, _ *registry.Registry, s release.Store) {
346 rewriteClaim(t, s, func(op *hostclaim.Op) { op.ID = hsOther })
347 }, true},
348 "the claim leaves the move": {true, func(t *testing.T, _ *registry.Registry, s release.Store) {
349 rewriteClaim(t, s, func(op *hostclaim.Op) { op.Kind = hostclaim.KindDeleting })
350 }, true},
351 "the hold is released": {true, func(t *testing.T, reg *registry.Registry, _ release.Store) {
352 if ok, err := reg.ReleaseHold(hsHost, hsOp); err != nil || !ok {
353 t.Fatalf("ReleaseHold: %v %v", ok, err)
354 }
355 }, true},
356 "the hold passes to another op": {true, func(t *testing.T, reg *registry.Registry, _ release.Store) {
357 if ok, err := reg.ReleaseHold(hsHost, hsOp); err != nil || !ok {
358 t.Fatalf("ReleaseHold: %v %v", ok, err)
359 }
360 if err := reg.SetHold(hsHost, "landing", hsOther); err != nil {
361 t.Fatal(err)
362 }
363 }, true},
364 } {
365 t.Run(name, func(t *testing.T) {
366 ctx := context.Background()
367 s := release.NewDirStore(t.TempDir())
368 hsClaim(t, s, movingOp())
369 hsReport(t, s, hsOp, "v7")
370 shipV7(t, s)
371 reg := testRegistry(t)
372 if tc.row {
373 if err := reg.Upsert(heldRow); err != nil {
374 t.Fatal(err)
375 }
376 }
377 opt := t.TempDir()
378 auth := heldStageAuthority(reg, s, hsBox, t.TempDir())
379 req := hsRequest()
380 if err := auth(req, nil); err != nil {
381 t.Fatalf("authority before staging: %v", err)
382 }
383 rt := registry.Route{Host: req.Host, Account: req.Account, App: req.App, HoldOp: req.Op}
384 if _, err := release.StageHeldVerified(ctx, s, hsPub, hsRatchet(t), opt, rt, req.Version, offProofGate(t)); err != nil {
385 t.Fatalf("StageHeld: %v", err)
386 }
387 tc.during(t, reg, s)
388 _, err := release.MaterializeHeld(ctx, s, hsPub, hsRatchet(t), opt, rt, req.Version, offProofGate(t), func(publish func() error) error { return auth(req, publish) })
389 got, _ := release.LiveVersion(opt, hsHost)
390 if tc.stale {
391 if !errors.Is(err, release.ErrStaleHeldStage) || got != "" {
392 t.Fatalf("PublishHeld: %v, live %q; want ErrStaleHeldStage and no live link", err, got)
393 }
394 return
395 }
396 if err != nil || got != "v7" {
397 t.Fatalf("PublishHeld: %v, live %q; want v7", err, got)
398 }
399 })
400 }
401}
402
403// TestHeldPublicationRechecksInsideTheFreeze: a change that lands after the
404// full check and before the freeze is caught by the re-check made with the
405// holds frozen, and the publication itself runs while they are frozen: a
406// ReleaseHold issued during it waits until it returns.
407func TestHeldPublicationRechecksInsideTheFreeze(t *testing.T) {
408 heldRow := registry.Route{Host: hsHost, Kind: "instance", App: "titogo", Account: "bab", Status: "landing", HoldOp: hsOp}
409 for name, tc := range map[string]struct {
410 row bool
411 window func(*testing.T, *registry.Registry, release.Store)
412 }{
413 "the hold is released": {true, func(t *testing.T, reg *registry.Registry, _ release.Store) {
414 if ok, err := reg.ReleaseHold(hsHost, hsOp); err != nil || !ok {
415 t.Fatalf("ReleaseHold: %v %v", ok, err)
416 }
417 }},
418 "a row appears held by another op": {false, func(t *testing.T, reg *registry.Registry, _ release.Store) {
419 r := heldRow
420 r.HoldOp = hsOther
421 if err := reg.Upsert(r); err != nil {
422 t.Fatal(err)
423 }
424 }},
425 "the claim moves to another op": {false, func(t *testing.T, _ *registry.Registry, s release.Store) {
426 rewriteClaim(t, s, func(op *hostclaim.Op) { op.ID = hsOther })
427 }},
428 "the claim retargets": {true, func(t *testing.T, _ *registry.Registry, s release.Store) {
429 rewriteClaim(t, s, func(op *hostclaim.Op) { op.Intent["target"] = "fr-box" })
430 }},
431 } {
432 t.Run(name, func(t *testing.T) {
433 s := release.NewDirStore(t.TempDir())
434 hsClaim(t, s, movingOp())
435 hsReport(t, s, hsOp, "v7")
436 reg := testRegistry(t)
437 if tc.row {
438 if err := reg.Upsert(heldRow); err != nil {
439 t.Fatal(err)
440 }
441 }
442 orig := heldStageBeforeFreeze
443 t.Cleanup(func() { heldStageBeforeFreeze = orig })
444 heldStageBeforeFreeze = func() { tc.window(t, reg, s) }
445 published := false
446 err := heldStageAuthority(reg, s, hsBox, t.TempDir())(hsRequest(), func() error { published = true; return nil })
447 if !errors.Is(err, release.ErrStaleHeldStage) || published {
448 t.Fatalf("authority: %v (published %v), want ErrStaleHeldStage before publishing", err, published)
449 }
450 })
451 }
452
453 t.Run("publication runs with the holds frozen", func(t *testing.T) {
454 s := release.NewDirStore(t.TempDir())
455 hsClaim(t, s, movingOp())
456 hsReport(t, s, hsOp, "v7")
457 reg := testRegistry(t)
458 if err := reg.Upsert(heldRow); err != nil {
459 t.Fatal(err)
460 }
461 released := make(chan struct{})
462 err := heldStageAuthority(reg, s, hsBox, t.TempDir())(hsRequest(), func() error {
463 go func() { _, _ = reg.ReleaseHold(hsHost, hsOp); close(released) }()
464 select {
465 case <-released:
466 return errors.New("a ReleaseHold landed during the publication")
467 case <-time.After(300 * time.Millisecond):
468 return nil
469 }
470 })
471 if err != nil {
472 t.Fatal(err)
473 }
474 select {
475 case <-released:
476 case <-time.After(5 * time.Second):
477 t.Fatal("the ReleaseHold never ran after the freeze")
478 }
479 })
480}
481
482// shipUnpromoted ships bab/app's version for this platform and promotes it
483// nowhere: no promotion record and no pointer authorizes it.
484func shipUnpromoted(t *testing.T, s release.Store, app, version string) {
485 t.Helper()
486 plat := runtime.GOOS + "-" + runtime.GOARCH
487 m := release.Manifest{App: app, Version: version, Kind: "binary", Artifacts: map[string]release.Artifact{plat: {}}}
488 if err := release.Ship(context.Background(), s, "bab", m, map[string][]byte{plat: []byte("pinned")}); err != nil {
489 t.Fatal(err)
490 }
491}
492
493// Settings spec rev 11 §4.3a, root's side: a request the move's authority
494// accepts, for a build no promotion record and no pointer authorizes, is
495// dropped before anything is downloaded, and live/<host> is never written.
496func TestHeldStagePassDropsAnUnauthorizedBuild(t *testing.T) {
497 ctx := context.Background()
498 s := release.NewDirStore(t.TempDir())
499 hsClaim(t, s, movingOp())
500 hsReport(t, s, hsOp, "v7")
501 shipUnpromoted(t, s, "titogo", "v7")
502 dd, opt := t.TempDir(), t.TempDir()
503 reqDir := release.HeldStageDir(dd)
504 if err := os.MkdirAll(reqDir, 0o755); err != nil {
505 t.Fatal(err)
506 }
507 raw, _ := json.Marshal(hsRequest())
508 if err := os.WriteFile(filepath.Join(reqDir, hsHost+".json"), raw, 0o644); err != nil {
509 t.Fatal(err)
510 }
511 heldStagePass(ctx, dd, opt, hsPub, hsRatchet(t), testRegistry(t), s, hsBox)
512 if got, _ := release.LiveVersion(opt, hsHost); got != "" {
513 t.Fatalf("an unauthorized build went live: %q", got)
514 }
515 if _, err := os.Stat(filepath.Join(opt, "versions")); !errors.Is(err, os.ErrNotExist) {
516 t.Fatalf("an unauthorized build was staged: %v", err)
517 }
518 if _, err := os.Stat(filepath.Join(reqDir, hsHost+".json")); !errors.Is(err, os.ErrNotExist) {
519 t.Fatalf("the refused request was kept for a retry: %v", err)
520 }
521}
522
523// Settings spec rev 11 §4.3a, the agent's own path (ruling R5): T7 stages
524// only a build §4.1 authorizes. A shipped, never-promoted build, or any
525// build when the release key did not parse (a nil pub), fails T7 with
526// nothing staged and nothing relayed to the root pass.
527func TestHeldReleaseReadyRefusesAnUnauthorizedBuild(t *testing.T) {
528 t.Parallel()
529 ctx := context.Background()
530 for name, tc := range map[string]struct {
531 promote bool
532 pub ed25519.PublicKey
533 }{
534 "never promoted": {false, hsPub},
535 "no release key": {true, nil},
536 } {
537 t.Run(name, func(t *testing.T) {
538 h := newPhase1Harness(t)
539 rec := h.start(t, "manual")
540 shipUnpromoted(t, h.s, "app", "v7")
541 if tc.promote {
542 promoteHeld(t, h.s, "app", "v7")
543 }
544 h.pass(t)
545 data, opt := t.TempDir(), t.TempDir()
546 relayed := false
547 ready := heldReleaseReady(testRegistry(t), opt, data, "uk-box", tc.pub, hsRatchet(t), offProofGate(t), func(string, string, error) { relayed = true })
548 if c := ready(ctx, h.s, rec, "v7"); c.OK || c.Reason != t7Failed {
549 t.Fatalf("T7 = %+v, want %q", c, t7Failed)
550 }
551 if _, err := os.Stat(filepath.Join(opt, "versions")); !errors.Is(err, os.ErrNotExist) {
552 t.Fatalf("an unauthorized build was staged: %v", err)
553 }
554 if _, err := os.Stat(filepath.Join(release.HeldStageDir(data), p1Host+".json")); !errors.Is(err, os.ErrNotExist) || relayed {
555 t.Fatalf("an unauthorized build was relayed to the root pass (%v, %v)", err, relayed)
556 }
557 if v, _ := release.LiveVersion(opt, p1Host); v != "" {
558 t.Fatalf("live = %q, want no live link", v)
559 }
560 })
561 }
562}
563