oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package provisioner
4
5import (
6 "bytes"
7 "context"
8 "errors"
9 "os"
10 "os/exec"
11 "strings"
12 "testing"
13 "time"
14
15 "amadan.net/oficina/home/internal/operator"
16 "github.com/go-json-experiment/json"
17 "github.com/go-json-experiment/json/jsontext"
18)
19
20func planFixture() InstallationPlan {
21 target := func(host, origin string) InstallationTarget {
22 return InstallationTarget{Console: "https://console.example.test", Account: "synthetic-account", App: "oficina/home", Host: host, Origin: origin, Release: strings.Repeat("a", 40), Artifact: strings.Repeat("b", 64), Environment: strings.Repeat("c", 64), DataKey: strings.Repeat("d", 64)}
23 }
24 return InstallationPlan{1, testToken(20), testToken(21), target("credential.example.test", "https://id.example.test"), target("directory.example.test", "https://home.example.test")}
25}
26func stateFixture(t *testing.T) (*journal, InstallationPlan) {
27 t.Helper()
28 j := journalFixture(t)
29 p := planFixture()
30 j.now = func() time.Time { return time.Unix(2000000000, 0) }
31 if e := j.enroll(context.Background(), p); e != nil {
32 t.Fatal(e)
33 }
34 return j, p
35}
36func snapshot(t *testing.T, j *journal) *installationBook {
37 t.Helper()
38 j.mu.Lock()
39 defer j.mu.Unlock()
40 b, e := j.bookLocked()
41 if e != nil {
42 t.Fatal(e)
43 }
44 return b
45}
46func initialization(op string) operator.InitializeInput {
47 return operator.InitializeInput{InputBase: operator.InputBase{Version: 1, Operation: "directory.initialize", OperationID: op}, Generation: testToken(22), Baseline: "operator-attests-never-provisioned"}
48}
49func TestInstallationPreparationReplayAndIndependentRoles(t *testing.T) {
50 j, p := stateFixture(t)
51 ctx := context.Background()
52 if e := j.enroll(ctx, p); e != nil {
53 t.Fatal(e)
54 }
55 changed := p
56 changed.Directory.Environment = strings.Repeat("e", 64)
57 if e := j.enroll(ctx, changed); e != ErrRefused {
58 t.Fatal("changed baseline", e)
59 }
60 raw, e := j.prepare(ctx, p.Installation, testToken(23), "directory.initialize", initialization(testToken(23)))
61 if e != nil {
62 t.Fatal(e)
63 }
64 before := snapshot(t, j)
65 req := before.installations[p.Installation].Operations[testToken(23)].Request
66 if req.Authorization().JournalSequence != 2 || req.Authorization().Sequence != 1 {
67 t.Fatal("independent sequences")
68 }
69 if before.installations[p.Installation].Generation != "" || before.installations[p.Installation].Heads["directory"].Sequence != 0 {
70 t.Fatal("preparation advanced authority")
71 }
72 j.now = func() time.Time { return time.Unix(2000001000, 0) }
73 repeated, e := j.prepare(ctx, p.Installation, testToken(23), "directory.initialize", initialization(testToken(23)))
74 if e != nil || !bytes.Equal(raw, repeated) {
75 t.Fatal("exact expired prepared request changed", e)
76 }
77 changedInput := initialization(testToken(23))
78 changedInput.Generation = testToken(29)
79 if _, e = j.prepare(ctx, p.Installation, testToken(23), "directory.initialize", changedInput); e != ErrRefused {
80 t.Fatal("changed input", e)
81 }
82 if _, e = j.prepare(ctx, p.Installation, testToken(24), "directory.initialize", initialization(testToken(24))); e != ErrRefused {
83 t.Fatal("replaced unresolved attempt", e)
84 }
85 if e = j.markInFlight(ctx, p.Installation, testToken(25), testToken(23)); e != ErrRefused {
86 t.Fatal("expired dispatch", e)
87 }
88 credential, e := j.prepare(ctx, p.Installation, testToken(26), "credential.owner-seed", struct {
89 Version int `json:"version"`
90 }{1})
91 if e != nil || len(credential) == 0 {
92 t.Fatal("independent credential role", e)
93 }
94 after := snapshot(t, j)
95 if after.installations[p.Installation].Operations[testToken(26)].Request.Authorization().Sequence != 1 || len(after.installations[p.Installation].Heads) != 0 {
96 t.Fatal("role advanced during preparation")
97 }
98}
99func TestInstallationAmbiguityNeverReleasesSlot(t *testing.T) {
100 j, p := stateFixture(t)
101 ctx := context.Background()
102 op := testToken(23)
103 if _, e := j.prepare(ctx, p.Installation, op, "directory.initialize", initialization(op)); e != nil {
104 t.Fatal(e)
105 }
106 if e := j.markInFlight(ctx, p.Installation, testToken(24), op); e != nil {
107 t.Fatal(e)
108 }
109 if e := j.markInFlight(ctx, p.Installation, testToken(24), op); e != nil {
110 t.Fatal("event replay", e)
111 }
112 if e := j.transition(ctx, p.Installation, testToken(25), "provisioner.outcome", "in-flight", localTransition{Operation: op, Kind: "ambiguous"}); e != nil {
113 t.Fatal(e)
114 }
115 j.now = func() time.Time { return time.Unix(2000001000, 0) }
116 if e := j.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", localTransition{Operation: op, Kind: "refused"}); e != ErrRefused {
117 t.Fatal("ambiguous became refused", e)
118 }
119 if _, e := j.prepare(ctx, p.Installation, testToken(27), "directory.initialize", initialization(testToken(27))); e != ErrRefused {
120 t.Fatal("replaced ambiguous", e)
121 }
122}
123func TestInstallationRefusalRequiresExpiryBeforeReplacement(t *testing.T) {
124 j, p := stateFixture(t)
125 ctx := context.Background()
126 op := testToken(23)
127 if _, e := j.prepare(ctx, p.Installation, op, "directory.initialize", initialization(op)); e != nil {
128 t.Fatal(e)
129 }
130 if e := j.markInFlight(ctx, p.Installation, testToken(24), op); e != nil {
131 t.Fatal(e)
132 }
133 tr := localTransition{Operation: op, Kind: "refused"}
134 if e := j.transition(ctx, p.Installation, testToken(25), "provisioner.outcome", "in-flight", tr); e != ErrRefused {
135 t.Fatal("fresh slot released", e)
136 }
137 j.now = func() time.Time { return time.Unix(2000000300, 0) }
138 if e := j.transition(ctx, p.Installation, testToken(25), "provisioner.outcome", "in-flight", tr); e != nil {
139 t.Fatal(e)
140 }
141 if _, e := j.prepare(ctx, p.Installation, testToken(26), "directory.initialize", initialization(testToken(26))); e != nil {
142 t.Fatal(e)
143 }
144 s := snapshot(t, j).installations[p.Installation]
145 if s.Operations[testToken(26)].Request.Authorization().Sequence != 1 || s.Heads["directory"].Sequence != 0 {
146 t.Fatal("refusal consumed role sequence")
147 }
148}
149func TestInstallationExactLargeResultDoesNotAdvanceUntilTransition(t *testing.T) {
150 j, p := stateFixture(t)
151 ctx := context.Background()
152 op := testToken(23)
153 if _, e := j.prepare(ctx, p.Installation, op, "directory.initialize", initialization(op)); e != nil {
154 t.Fatal(e)
155 }
156 if e := j.markInFlight(ctx, p.Installation, testToken(24), op); e != nil {
157 t.Fatal(e)
158 }
159 result := operator.Result{Version: 1, OperationID: op, Outcome: "committed", Generation: testToken(22), Receipt: jsontext.Value(`"x"`)}
160 small, _ := json.Marshal(result, json.Deterministic(true))
161 result.Receipt = jsontext.Value(`"` + strings.Repeat("x", (64<<10)-len(small)+1) + `"`)
162 exact, e := json.Marshal(result, json.Deterministic(true))
163 if e != nil || len(exact) != 64<<10 {
164 t.Fatal("boundary", len(exact), e)
165 }
166 record, e := j.append(ctx, p.Installation, testToken(25), "provisioner.result", "in-flight", exact)
167 if e != nil {
168 t.Fatal(e)
169 }
170 s := snapshot(t, j).installations[p.Installation]
171 if s.Generation != "" || s.Heads["directory"].Sequence != 0 {
172 t.Fatal("orphan result advanced")
173 }
174 tr := localTransition{Operation: op, Kind: "accepted", ResultRecord: testToken(25), ResultHead: testToken(99)}
175 if e = j.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", tr); e != ErrRefused {
176 t.Fatal("tampered result reference", e)
177 }
178 tr.ResultHead = hash(record)
179 if e = j.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", tr); e != nil {
180 t.Fatal(e)
181 }
182 if e = j.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", tr); e != nil {
183 t.Fatal("outcome exact replay", e)
184 }
185 s = snapshot(t, j).installations[p.Installation]
186 if s.Generation != testToken(22) || s.Heads["directory"].Sequence != 1 || !bytes.Equal(s.Operations[op].Result, exact) {
187 t.Fatal("accepted result changed")
188 }
189 if _, e = j.prepare(ctx, p.Installation, testToken(27), "directory.seed-team", struct {
190 Generation string `json:"expected_generation"`
191 }{s.Generation}); e != nil {
192 t.Fatal(e)
193 }
194 s = snapshot(t, j).installations[p.Installation]
195 if s.Operations[testToken(27)].Request.Authorization().Previous != s.Heads["directory"].Head {
196 t.Fatal("accepted chain not bound")
197 }
198}
199func TestInstallationPreparationCrashBoundaries(t *testing.T) {
200 for _, boundary := range []string{"prepared", "floor", "finalized"} {
201 t.Run(boundary, func(t *testing.T) {
202 j, p := stateFixture(t)
203 ctx := context.Background()
204 stop := errors.New("synthetic crash")
205 j.boundary = func(stage string) error {
206 if stage == boundary {
207 return stop
208 }
209 return nil
210 }
211 if _, e := j.prepare(ctx, p.Installation, testToken(23), "directory.initialize", initialization(testToken(23))); e != stop {
212 t.Fatal(e)
213 }
214 j.close()
215 reopened, e := openJournal(j.dir, j.floor, j.key, false)
216 if e != nil {
217 t.Fatal(e)
218 }
219 defer reopened.close()
220 reopened.now = func() time.Time { return time.Unix(2000001000, 0) }
221 raw, e := reopened.prepare(ctx, p.Installation, testToken(23), "directory.initialize", initialization(testToken(23)))
222 if e != nil || len(raw) == 0 {
223 t.Fatal("recover signed bytes", e)
224 }
225 s := snapshot(t, reopened).installations[p.Installation]
226 if len(s.Operations) != 1 || s.Heads["directory"].Sequence != 0 || s.Operations[testToken(23)].Request.Authorization().JournalSequence != 2 {
227 t.Fatal("crash reserved twice")
228 }
229 })
230 }
231}
232
233// These subprocesses exercise local custody and journal transitions only. They
234// do not impersonate a worker or manufacture an authenticated remote outcome.
235func TestNativeEnrollmentCLIAndProcessDeath(t *testing.T) {
236 if mode := os.Getenv("OFICINA_STATE_CHILD"); mode != "" {
237 state, floor := os.Getenv("OFICINA_STATE_PATH"), os.Getenv("OFICINA_FLOOR_PATH")
238 if mode == "enroll" {
239 if e := Run([]string{"provision", "enroll", "--state", state, "--floor-dir", floor, "--password-fd", "3", "--plan-fd", "4"}, os.Stdout); e != nil {
240 os.Exit(1)
241 }
242 os.Exit(0)
243 }
244 password, e := privatePassword(3)
245 if e != nil {
246 os.Exit(2)
247 }
248 j, e := existingJournal(state, floor, password)
249 clear(password)
250 if e != nil {
251 os.Exit(3)
252 }
253 defer closeExisting(j)
254 j.boundary = func(stage string) error {
255 if stage == mode {
256 os.Exit(72)
257 }
258 return nil
259 }
260 _, e = j.prepare(context.Background(), testToken(20), testToken(23), "directory.initialize", initialization(testToken(23)))
261 if e != nil {
262 os.Exit(4)
263 }
264 os.Exit(5)
265 }
266 for _, boundary := range []string{"prepared", "floor", "finalized"} {
267 t.Run(boundary, func(t *testing.T) {
268 state, floor := persistentDirectory(t), persistentDirectory(t)
269 password := []byte("synthetic-state-private-password")
270 if _, e := Initialize(state, floor, password); e != nil {
271 t.Fatal(e)
272 }
273 plan, _ := canonicalJSON(planFixture())
274 run := func(mode string) ([]byte, error) {
275 r, w, e := os.Pipe()
276 if e != nil {
277 t.Fatal(e)
278 }
279 pr, pw, e := os.Pipe()
280 if e != nil {
281 t.Fatal(e)
282 }
283 defer r.Close()
284 defer pr.Close()
285 cmd := exec.Command(os.Args[0], "-test.run=^TestNativeEnrollmentCLIAndProcessDeath$")
286 cmd.Env = append(os.Environ(), "OFICINA_STATE_CHILD="+mode, "OFICINA_STATE_PATH="+state, "OFICINA_FLOOR_PATH="+floor)
287 cmd.ExtraFiles = []*os.File{r, pr}
288 var out bytes.Buffer
289 cmd.Stdout = &out
290 cmd.Stderr = &out
291 if e = cmd.Start(); e != nil {
292 t.Fatal(e)
293 }
294 r.Close()
295 pr.Close()
296 _, e = w.Write(password)
297 w.Close()
298 if e != nil {
299 t.Fatal(e)
300 }
301 if mode == "enroll" {
302 if _, e = pw.Write(plan); e != nil {
303 t.Fatal(e)
304 }
305 }
306 pw.Close()
307 e = cmd.Wait()
308 if bytes.Contains(out.Bytes(), password) || bytes.Contains(out.Bytes(), []byte(planFixture().Credential.Host)) {
309 t.Fatal("private output")
310 }
311 return out.Bytes(), e
312 }
313 first, e := run("enroll")
314 if e != nil {
315 t.Fatal("enroll CLI", e)
316 }
317 second, e := run("enroll")
318 if e != nil || !bytes.Equal(first, second) {
319 t.Fatal("plan replay", e)
320 }
321 j, e := existingJournal(state, floor, password)
322 if e != nil {
323 t.Fatal(e)
324 }
325 if _, e = run("enroll"); e == nil {
326 t.Fatal("second process bypassed registry lock")
327 }
328 closeExisting(j)
329 _, e = run(boundary)
330 var exit *exec.ExitError
331 if !errors.As(e, &exit) || exit.ExitCode() != 72 {
332 t.Fatal("process death boundary", e)
333 }
334 reopened, e := existingJournal(state, floor, password)
335 if e != nil {
336 t.Fatal(e)
337 }
338 defer closeExisting(reopened)
339 reopened.now = func() time.Time { return time.Now().Add(time.Hour) }
340 raw, e := reopened.prepare(context.Background(), testToken(20), testToken(23), "directory.initialize", initialization(testToken(23)))
341 if e != nil || len(raw) == 0 {
342 t.Fatal("recover after process death", e)
343 }
344 s := snapshot(t, reopened).installations[testToken(20)]
345 if len(s.Operations) != 1 || s.Heads["directory"].Sequence != 0 {
346 t.Fatal("process crash advanced authority")
347 }
348 })
349 }
350}
351
352func TestInstallationResultAndAcceptanceCrashBoundaries(t *testing.T) {
353 for _, phase := range []string{"result", "acceptance"} {
354 for _, boundary := range []string{"prepared", "floor", "finalized"} {
355 t.Run(phase+"/"+boundary, func(t *testing.T) {
356 j, p := stateFixture(t)
357 ctx := context.Background()
358 op := testToken(23)
359 if _, e := j.prepare(ctx, p.Installation, op, "directory.initialize", initialization(op)); e != nil {
360 t.Fatal(e)
361 }
362 if e := j.markInFlight(ctx, p.Installation, testToken(24), op); e != nil {
363 t.Fatal(e)
364 }
365 result, _ := json.Marshal(operator.Result{Version: 1, OperationID: op, Outcome: "committed", Generation: testToken(22)}, json.Deterministic(true))
366 stop := errors.New("synthetic interruption")
367 inject := func() {
368 j.boundary = func(stage string) error {
369 if stage == boundary {
370 return stop
371 }
372 return nil
373 }
374 }
375 if phase == "result" {
376 inject()
377 }
378 record, e := j.append(ctx, p.Installation, testToken(25), "provisioner.result", "in-flight", result)
379 if phase == "result" && e != stop || phase == "acceptance" && e != nil {
380 t.Fatal(e)
381 }
382 if phase == "acceptance" {
383 inject()
384 if e = j.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", localTransition{Operation: op, Kind: "accepted", ResultRecord: testToken(25), ResultHead: hash(record)}); e != stop {
385 t.Fatal(e)
386 }
387 }
388 j.close()
389 reopened, e := openJournal(j.dir, j.floor, j.key, false)
390 if e != nil {
391 t.Fatal(e)
392 }
393 defer reopened.close()
394 b := snapshot(t, reopened)
395 s := b.installations[p.Installation]
396 if phase == "result" && (s.Generation != "" || s.Heads["directory"].Sequence != 0) {
397 t.Fatal("orphan accepted")
398 }
399 if phase == "acceptance" && (s.Generation != testToken(22) || s.Heads["directory"].Sequence != 1) {
400 t.Fatal("accepted event lost")
401 }
402 tr := localTransition{Operation: op, Kind: "accepted", ResultRecord: testToken(25), ResultHead: b.heads[testToken(25)]}
403 if phase == "result" {
404 missing := tr
405 missing.ResultRecord = testToken(30)
406 if e = reopened.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", missing); e != ErrRefused {
407 t.Fatal("missing result accepted", e)
408 }
409 }
410 if e = reopened.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", tr); e != nil {
411 t.Fatal("exact acceptance recovery", e)
412 }
413 if snapshot(t, reopened).installations[p.Installation].Heads["directory"].Sequence != 1 {
414 t.Fatal("accepted twice")
415 }
416 })
417 }
418 }
419}
420func TestInstallationRestoredOldJournalCannotForgetAcceptedHead(t *testing.T) {
421 j, p := stateFixture(t)
422 ctx := context.Background()
423 op := testToken(23)
424 if _, e := j.prepare(ctx, p.Installation, op, "directory.initialize", initialization(op)); e != nil {
425 t.Fatal(e)
426 }
427 if _, e := j.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); e != nil {
428 t.Fatal(e)
429 }
430 path := j.dir.file.Name() + "/registry.db"
431 old, e := os.ReadFile(path)
432 if e != nil {
433 t.Fatal(e)
434 }
435 if e = j.markInFlight(ctx, p.Installation, testToken(24), op); e != nil {
436 t.Fatal(e)
437 }
438 result, _ := json.Marshal(operator.Result{Version: 1, OperationID: op, Outcome: "committed", Generation: testToken(22)}, json.Deterministic(true))
439 record, e := j.append(ctx, p.Installation, testToken(25), "provisioner.result", "in-flight", result)
440 if e != nil {
441 t.Fatal(e)
442 }
443 if e = j.transition(ctx, p.Installation, testToken(26), "provisioner.outcome", "in-flight", localTransition{Operation: op, Kind: "accepted", ResultRecord: testToken(25), ResultHead: hash(record)}); e != nil {
444 t.Fatal(e)
445 }
446 j.close()
447 if e = os.WriteFile(path, old, 0600); e != nil {
448 t.Fatal(e)
449 }
450 reopened, e := openJournal(j.dir, j.floor, j.key, false)
451 if reopened != nil {
452 reopened.close()
453 }
454 if e != ErrRefused {
455 t.Fatal("old snapshot reset role authority", e)
456 }
457}
458
459func TestInstallationPrivatePlanAndRequestStayEncrypted(t *testing.T) {
460 j, p := stateFixture(t)
461 input := operator.OwnerInput{InputBase: operator.InputBase{Version: 1, Operation: "credential.owner-seed", OperationID: testToken(23)}, Directory: p.Directory.Origin, TeamID: 1, TeamSQID: "Uk", Address: "native-state-private-32@example.invalid", ExistingOnly: true}
462 if _, e := j.prepare(context.Background(), p.Installation, testToken(23), "credential.owner-seed", input); e != nil {
463 t.Fatal(e)
464 }
465 for _, suffix := range []string{"", "-wal", "-shm"} {
466 raw, e := os.ReadFile(j.dir.file.Name() + "/registry.db" + suffix)
467 if e != nil && !os.IsNotExist(e) {
468 t.Fatal(e)
469 }
470 for _, canary := range []string{input.Address, p.Directory.Host, p.Credential.Host, p.Directory.Environment, p.Directory.Origin} {
471 if bytes.Contains(raw, []byte(canary)) {
472 t.Fatal("private journal plaintext")
473 }
474 }
475 }
476 for _, action := range []string{"accept", "refuse", "complete", "mark-stopped"} {
477 if e := Run([]string{"provision", action}, &bytes.Buffer{}); e != ErrRefused {
478 t.Fatal("CLI manufactured completion", e)
479 }
480 }
481}
482