carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "bytes"
5 "context"
6 "crypto/ed25519"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "os"
11 "path/filepath"
12 "runtime"
13 "sync"
14 "syscall"
15 "testing"
16
17 "github.com/carlosframework/platform/internal/registry"
18)
19
20const (
21 heldTestHost = "held.example.com"
22 heldTestOp = "mv_abcdefghijklmnopqrstuvwxyz"
23)
24
25// heldPriv signs every held fixture's pointers and promotion records, and
26// heldPub verifies them: the release key a held stage is handed.
27var (
28 heldPriv = ed25519.NewKeyFromSeed(bytes.Repeat([]byte{7}, ed25519.SeedSize))
29 heldPub = heldPriv.Public().(ed25519.PublicKey)
30)
31
32// heldFixture ships two builds of bab/console and moves the stable channel
33// to the newer one: the pinned release (v1) is deliberately NOT the head.
34// v1 was promoted to canary/x before that, so it has a promotion record and
35// a held stage of it is §4.1-authorized (settings spec rev 11 §4.3a).
36func heldFixture(t *testing.T) (context.Context, Store, string) {
37 t.Helper()
38 ctx := context.Background()
39 s := NewDirStore(t.TempDir())
40 shipRelease(t, s, "bab", "console", "v1", adoptPlatform, []byte("pinned build"))
41 shipRelease(t, s, "bab", "console", "v2", adoptPlatform, []byte("channel head"))
42 if err := Promote(ctx, s, heldPriv, "bab", "console", "canary/x", "v1"); err != nil {
43 t.Fatalf("Promote: %v", err)
44 }
45 if err := Promote(ctx, s, heldPriv, "bab", "console", "stable", "v2"); err != nil {
46 t.Fatalf("Promote: %v", err)
47 }
48 return ctx, s, t.TempDir()
49}
50
51func heldRoute() registry.Route {
52 return registry.Route{Host: heldTestHost, Kind: "instance", App: "console", Account: "bab", Channel: "stable", Status: "landing", HoldOp: heldTestOp}
53}
54
55// TestMaterializeHeldStagesThePinnedVersion: the held row runs exactly the
56// pinned release, never the channel head, and LiveVersion reads it back.
57func TestMaterializeHeldStagesThePinnedVersion(t *testing.T) {
58 ctx, s, opt := heldFixture(t)
59 m, err := MaterializeHeld(ctx, s, heldPub, nr(t), opt, heldRoute(), "v1", offGate(t), openGuard)
60 if err != nil {
61 t.Fatalf("MaterializeHeld: %v", err)
62 }
63 if m == nil || m.Version != "v1" {
64 t.Fatalf("manifest = %+v, want v1's", m)
65 }
66 if got, err := LiveVersion(opt, heldTestHost); err != nil || got != "v1" {
67 t.Fatalf("LiveVersion = %q, %v; want the pinned v1, not the channel head", got, err)
68 }
69 body, err := os.ReadFile(LiveLink(opt, heldTestHost))
70 if err != nil || string(body) != "pinned build" {
71 t.Fatalf("live binary = %q, %v", body, err)
72 }
73}
74
75// TestMaterializeHeldStageBlocked: a process that cannot write the version
76// store gets ErrStageBlocked wrapping the cause and writes nothing. EROFS is
77// what ProtectSystem=strict answers, and it is not fs.ErrPermission.
78func TestMaterializeHeldStageBlocked(t *testing.T) {
79 for _, errno := range []syscall.Errno{syscall.EROFS, syscall.EACCES} {
80 t.Run(errno.Error(), func(t *testing.T) {
81 ctx, s, opt := heldFixture(t)
82 orig := materializeHeldStage
83 t.Cleanup(func() { materializeHeldStage = orig })
84 materializeHeldStage = func(context.Context, Store, string, string, string, string, string, string) (*Manifest, error) {
85 return nil, &os.PathError{Op: "mkdir", Path: filepath.Join(opt, "versions"), Err: errno}
86 }
87 _, err := MaterializeHeld(ctx, s, heldPub, nr(t), opt, heldRoute(), "v1", offGate(t), openGuard)
88 if !errors.Is(err, ErrStageBlocked) || !errors.Is(err, errno) {
89 t.Fatalf("MaterializeHeld: %v, want ErrStageBlocked wrapping %v", err, errno)
90 }
91 if ents, _ := os.ReadDir(opt); len(ents) != 0 {
92 t.Fatalf("optDir is not empty: %v", ents)
93 }
94 })
95 }
96 other := &os.PathError{Op: "open", Path: "x", Err: syscall.ENOENT}
97 if StageBlocked(other) || !StageBlocked(&os.PathError{Err: syscall.EPERM}) {
98 t.Fatal("StageBlocked classifies EROFS, EPERM and EACCES only")
99 }
100}
101
102// TestMaterializeHeldRefusesWhileHostClaimIsWired mirrors RenderHeld's
103// ruling F10: the live link is written outside any claim selection, so with
104// CARLOS_HOST_CLAIM on the call refuses and writes nothing.
105func TestMaterializeHeldRefusesWhileHostClaimIsWired(t *testing.T) {
106 ctx, s, opt := heldFixture(t)
107 t.Setenv("CARLOS_HOST_CLAIM", "1")
108 if _, err := MaterializeHeld(ctx, s, heldPub, nr(t), opt, heldRoute(), "v1", offGate(t), openGuard); !errors.Is(err, ErrHeldClaimWired) {
109 t.Fatalf("MaterializeHeld: %v, want ErrHeldClaimWired", err)
110 }
111 if ents, _ := os.ReadDir(opt); len(ents) != 0 {
112 t.Fatalf("optDir is not empty: %v", ents)
113 }
114}
115
116// heldRecordingStore counts every call that reaches the store underneath.
117type heldRecordingStore struct {
118 Store
119 mu sync.Mutex
120 calls []string
121}
122
123func (r *heldRecordingStore) note(s string) { r.mu.Lock(); r.calls = append(r.calls, s); r.mu.Unlock() }
124func (r *heldRecordingStore) Get(ctx context.Context, key string) ([]byte, string, error) {
125 r.note("get " + key)
126 return r.Store.Get(ctx, key)
127}
128func (r *heldRecordingStore) List(ctx context.Context, prefix string) ([]string, error) {
129 r.note("list " + prefix)
130 return r.Store.List(ctx, prefix)
131}
132
133func writeHeldRequest(t *testing.T, dataDir, name string, req HeldStageRequest) string {
134 t.Helper()
135 dir := HeldStageDir(dataDir)
136 if err := os.MkdirAll(dir, 0o755); err != nil {
137 t.Fatal(err)
138 }
139 raw, err := json.Marshal(req)
140 if err != nil {
141 t.Fatal(err)
142 }
143 path := filepath.Join(dir, name)
144 if err := os.WriteFile(path, raw, 0o644); err != nil {
145 t.Fatal(err)
146 }
147 return path
148}
149
150func goodHeldRequest() HeldStageRequest {
151 return HeldStageRequest{Account: "bab", App: "console", Host: heldTestHost, Version: "v1", Op: heldTestOp, Box: "uk-box"}
152}
153
154func gone(t *testing.T, path string) {
155 t.Helper()
156 if _, err := os.Lstat(path); !os.IsNotExist(err) {
157 t.Fatalf("%s is still there: %v", path, err)
158 }
159}
160
161// TestServeHeldStageRequestsRefusesStaleAndTraversal: the root side of the
162// held-stage relay validates every component before any filesystem effect,
163// asks authorize, and only then materializes. A request it refuses is
164// removed and never repoints live/<host>.
165func TestServeHeldStageRequestsRefusesStaleAndTraversal(t *testing.T) {
166 t.Run("no request directory is a no-op", func(t *testing.T) {
167 ctx, s, opt := heldFixture(t)
168 rec := &heldRecordingStore{Store: s}
169 dd := t.TempDir()
170 called := false
171 if err := ServeHeldStageRequests(ctx, rec, heldPub, nr(t), opt, dd, offGate(t), passAuth(func(HeldStageRequest) { called = true })); err != nil {
172 t.Fatalf("ServeHeldStageRequests: %v", err)
173 }
174 if called || len(rec.calls) != 0 {
175 t.Fatalf("authorize called %v, store calls %v", called, rec.calls)
176 }
177 if ents, _ := os.ReadDir(dd); len(ents) != 0 {
178 t.Fatalf("dataDir gained %v", ents)
179 }
180 if ents, _ := os.ReadDir(opt); len(ents) != 0 {
181 t.Fatalf("optDir gained %v", ents)
182 }
183 })
184
185 t.Run("an op the claim no longer names", func(t *testing.T) {
186 ctx, s, opt := heldFixture(t)
187 dd := t.TempDir()
188 req := goodHeldRequest()
189 req.Op = "mv_bcdefghijklmnopqrstuvwxyza"
190 path := writeHeldRequest(t, dd, req.Host+".json", req)
191 authorize := func(r HeldStageRequest, then func() error) error {
192 if r.Op != heldTestOp {
193 return fmt.Errorf("%w: the claim names %s", ErrStaleHeldStage, heldTestOp)
194 }
195 return runThen(then)
196 }
197 if err := ServeHeldStageRequests(ctx, s, heldPub, nr(t), opt, dd, offGate(t), authorize); err != nil {
198 t.Fatalf("ServeHeldStageRequests: %v", err)
199 }
200 gone(t, path)
201 gone(t, LiveLink(opt, heldTestHost))
202 gone(t, filepath.Join(HeldStagedDir(dd), heldTestHost+".json"))
203 })
204
205 for name, tc := range map[string]struct {
206 file string
207 mutate func(*HeldStageRequest)
208 }{
209 "host traverses": {"evil.json", func(r *HeldStageRequest) { r.Host = "../x" }},
210 "host is not the file name": {heldTestHost + ".json", func(r *HeldStageRequest) { r.Host = "other.example.com" }},
211 "host carries a NUL": {"nul.json", func(r *HeldStageRequest) { r.Host = "nul\x00" }},
212 "version traverses": {heldTestHost + ".json", func(r *HeldStageRequest) { r.Version = ".." }},
213 "account is malformed": {heldTestHost + ".json", func(r *HeldStageRequest) { r.Account = "../bab" }},
214 "app is malformed": {heldTestHost + ".json", func(r *HeldStageRequest) { r.App = "a/b" }},
215 "op is empty": {heldTestHost + ".json", func(r *HeldStageRequest) { r.Op = "" }},
216 "box is empty": {heldTestHost + ".json", func(r *HeldStageRequest) { r.Box = "" }},
217 } {
218 t.Run(name, func(t *testing.T) {
219 ctx, s, opt := heldFixture(t)
220 rec := &heldRecordingStore{Store: s}
221 dd := t.TempDir()
222 req := goodHeldRequest()
223 tc.mutate(&req)
224 path := writeHeldRequest(t, dd, tc.file, req)
225 called := false
226 if err := ServeHeldStageRequests(ctx, rec, heldPub, nr(t), opt, dd, offGate(t), passAuth(func(HeldStageRequest) { called = true })); err != nil {
227 t.Fatalf("ServeHeldStageRequests: %v", err)
228 }
229 gone(t, path)
230 if called || len(rec.calls) != 0 {
231 t.Fatalf("a refused request reached authorize (%v) or the store (%v)", called, rec.calls)
232 }
233 if ents, _ := os.ReadDir(opt); len(ents) != 0 {
234 t.Fatalf("optDir gained %v", ents)
235 }
236 })
237 }
238
239 t.Run("garbage is removed", func(t *testing.T) {
240 ctx, s, opt := heldFixture(t)
241 dd := t.TempDir()
242 path := filepath.Join(HeldStageDir(dd), heldTestHost+".json")
243 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
244 t.Fatal(err)
245 }
246 if err := os.WriteFile(path, []byte("{not json"), 0o644); err != nil {
247 t.Fatal(err)
248 }
249 if err := ServeHeldStageRequests(ctx, s, heldPub, nr(t), opt, dd, offGate(t), passAuth(nil)); err != nil {
250 t.Fatal(err)
251 }
252 gone(t, path)
253 })
254
255 t.Run("a symlinked request is removed, never followed", func(t *testing.T) {
256 ctx, s, opt := heldFixture(t)
257 dd := t.TempDir()
258 target := filepath.Join(t.TempDir(), "target.json")
259 raw, _ := json.Marshal(goodHeldRequest())
260 if err := os.WriteFile(target, raw, 0o644); err != nil {
261 t.Fatal(err)
262 }
263 if err := os.MkdirAll(HeldStageDir(dd), 0o755); err != nil {
264 t.Fatal(err)
265 }
266 link := filepath.Join(HeldStageDir(dd), heldTestHost+".json")
267 if err := os.Symlink(target, link); err != nil {
268 t.Fatal(err)
269 }
270 called := false
271 if err := ServeHeldStageRequests(ctx, s, heldPub, nr(t), opt, dd, offGate(t), passAuth(func(HeldStageRequest) { called = true })); err != nil {
272 t.Fatal(err)
273 }
274 gone(t, link)
275 if called {
276 t.Fatal("a symlinked request reached authorize")
277 }
278 if _, err := os.Stat(target); err != nil {
279 t.Fatalf("the link's target was touched: %v", err)
280 }
281 })
282
283 t.Run("a failed materialization keeps the request", func(t *testing.T) {
284 ctx, s, opt := heldFixture(t)
285 dd := t.TempDir()
286 req := goodHeldRequest()
287 req.Version = "v9" // never shipped
288 // ...but authorized (a signed record), so the stage fails after
289 // §4.1 lets it through, not at it: an unauthorized build is
290 // dropped (TestServeHeldStageRequestsDropsUnauthorizedBuilds).
291 if err := RootSigner(heldPriv).WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "console", Version: "v9",
292 ManifestDigest: ManifestDigest([]byte("v9")), Channel: "canary/x"}); err != nil {
293 t.Fatal(err)
294 }
295 path := writeHeldRequest(t, dd, req.Host+".json", req)
296 if err := ServeHeldStageRequests(ctx, s, heldPub, nr(t), opt, dd, offGate(t), passAuth(nil)); err != nil {
297 t.Fatal(err)
298 }
299 if _, err := os.Stat(path); err != nil {
300 t.Fatalf("the request was dropped after a failed stage: %v", err)
301 }
302 gone(t, LiveLink(opt, heldTestHost))
303 })
304
305 t.Run("the happy path materializes, acks and removes", func(t *testing.T) {
306 ctx, s, opt := heldFixture(t)
307 dd := t.TempDir()
308 req := goodHeldRequest()
309 path := writeHeldRequest(t, dd, req.Host+".json", req)
310 var seen []HeldStageRequest
311 if err := ServeHeldStageRequests(ctx, s, heldPub, nr(t), opt, dd, offGate(t), passAuth(func(r HeldStageRequest) { seen = append(seen, r) })); err != nil {
312 t.Fatalf("ServeHeldStageRequests: %v", err)
313 }
314 if len(seen) != 2 || seen[0] != req || seen[1] != req {
315 t.Fatalf("authorize saw %+v, want %+v before staging and again around publication", seen, req)
316 }
317 gone(t, path)
318 if got, err := LiveVersion(opt, heldTestHost); err != nil || got != "v1" {
319 t.Fatalf("LiveVersion = %q, %v; want v1", got, err)
320 }
321 raw, err := os.ReadFile(filepath.Join(HeldStagedDir(dd), heldTestHost+".json"))
322 if err != nil {
323 t.Fatalf("no held-staged ack: %v", err)
324 }
325 var ack HeldStaged
326 if err := json.Unmarshal(raw, &ack); err != nil || ack.Version != "v1" || ack.Op != heldTestOp || ack.At.IsZero() {
327 t.Fatalf("held-staged ack = %s (%v)", raw, err)
328 }
329 })
330}
331
332// openGuard publishes unconditionally: the guard of a test whose authority
333// is not the point.
334func openGuard(publish func() error) error { return publish() }
335
336func runThen(then func() error) error {
337 if then == nil {
338 return nil
339 }
340 return then()
341}
342
343// passAuth is an authority that accepts everything, noting each call.
344func passAuth(note func(HeldStageRequest)) func(HeldStageRequest, func() error) error {
345 return func(r HeldStageRequest, then func() error) error {
346 if note != nil {
347 note(r)
348 }
349 return runThen(then)
350 }
351}
352
353// TestMaterializeHeldNeedsAGuard: publication without an authority would
354// repoint live/<host> on the word of a request alone, so a nil guard is
355// refused after staging and before anything is published.
356func TestMaterializeHeldNeedsAGuard(t *testing.T) {
357 ctx, s, opt := heldFixture(t)
358 if _, err := MaterializeHeld(ctx, s, heldPub, nr(t), opt, heldRoute(), "v1", offGate(t), nil); err == nil {
359 t.Fatal("MaterializeHeld published with no guard")
360 }
361 gone(t, LiveLink(opt, heldTestHost))
362}
363
364// TestHeldPublicationRechecksItsAuthority: the authority that admitted a
365// request before staging is asked again around publication, and a move
366// aborted or replaced while the release was staging leaves live/<host>
367// untouched, drops the request, and reports ErrStaleHeldStage.
368func TestHeldPublicationRechecksItsAuthority(t *testing.T) {
369 ctx, s, opt := heldFixture(t)
370 orig := materializeHeldStage
371 t.Cleanup(func() { materializeHeldStage = orig })
372 staged := false
373 materializeHeldStage = func(ctx context.Context, s Store, optDir, acct, app, ver, host, want string) (*Manifest, error) {
374 m, err := orig(ctx, s, optDir, acct, app, ver, host, want)
375 staged = true // the move is replaced while this runs
376 return m, err
377 }
378 published := false
379 authorize := func(_ HeldStageRequest, then func() error) error {
380 if staged {
381 return fmt.Errorf("%w: the claim names another op", ErrStaleHeldStage)
382 }
383 return runThen(then)
384 }
385 _, err := MaterializeHeld(ctx, s, heldPub, nr(t), opt, heldRoute(), "v1", offGate(t), func(publish func() error) error {
386 return authorize(goodHeldRequest(), func() error { published = true; return publish() })
387 })
388 if !errors.Is(err, ErrStaleHeldStage) || published {
389 t.Fatalf("MaterializeHeld: %v (published %v), want ErrStaleHeldStage and nothing published", err, published)
390 }
391 gone(t, LiveLink(opt, heldTestHost))
392
393 staged = false
394 dd := t.TempDir()
395 path := writeHeldRequest(t, dd, heldTestHost+".json", goodHeldRequest())
396 if err := ServeHeldStageRequests(ctx, s, heldPub, nr(t), opt, dd, offGate(t), authorize); err != nil {
397 t.Fatal(err)
398 }
399 gone(t, path)
400 gone(t, LiveLink(opt, heldTestHost))
401 gone(t, filepath.Join(HeldStagedDir(dd), heldTestHost+".json"))
402}
403
404// offGate is a D3 gate on a box with no proof policy: the held stage's
405// behaviour is exactly what it was before D3-held.
406func offGate(t *testing.T) *ProofGate {
407 t.Helper()
408 return NewProofGate(filepath.Join(t.TempDir(), "proof-policy.json"), t.TempDir())
409}
410
411func TestHeldStageHardlinkedRequestIsRefused(t *testing.T) {
412 if runtime.GOOS != "linux" {
413 t.Skip("Linux descriptor boundary")
414 }
415 ctx, s, opt := heldFixture(t)
416 dd := t.TempDir()
417 target := filepath.Join(t.TempDir(), "target.json")
418 raw, err := json.Marshal(goodHeldRequest())
419 if err != nil {
420 t.Fatal(err)
421 }
422 if err := os.WriteFile(target, raw, 0o644); err != nil {
423 t.Fatal(err)
424 }
425 if err := os.MkdirAll(HeldStageDir(dd), 0o755); err != nil {
426 t.Fatal(err)
427 }
428 request := filepath.Join(HeldStageDir(dd), heldTestHost+".json")
429 if err := os.Link(target, request); err != nil {
430 t.Fatal(err)
431 }
432 called := false
433 if err := ServeHeldStageRequests(ctx, s, heldPub, nr(t), opt, dd, offGate(t), passAuth(func(HeldStageRequest) { called = true })); err != nil {
434 t.Fatal(err)
435 }
436 if called {
437 t.Fatal("hardlinked request reached authorize")
438 }
439 gone(t, request)
440 gone(t, filepath.Join(HeldStagedDir(dd), heldTestHost+".json"))
441 after, err := os.ReadFile(target)
442 if err != nil || string(after) != string(raw) {
443 t.Fatalf("hardlink target changed: %q %v", after, err)
444 }
445}
446