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 "database/sql"
9 "io"
10 "os"
11 "os/exec"
12 "strconv"
13 "testing"
14 "time"
15
16 "amadan.net/oficina/home/internal/operator"
17 "github.com/go-json-experiment/json"
18 "github.com/go-json-experiment/json/jsontext"
19 "golang.org/x/sys/unix"
20)
21
22func TestPrivateRequestDescriptorsSubprocess(t *testing.T) {
23 if mode := os.Getenv("OFICINA_DESCRIPTOR_CHILD"); mode != "" {
24 var output *requestOutput
25 var e error
26 if mode == "input-alias" {
27 output, e = privateRequestOutput(3, 4)
28 } else {
29 output, e = privateRequestOutput(3)
30 }
31 if mode != "valid" {
32 if output != nil {
33 output.Close()
34 }
35 if e != ErrRefused {
36 os.Exit(2)
37 }
38 os.Exit(0)
39 }
40 if e != nil {
41 os.Exit(3)
42 }
43 if e = writePrivateRequest(output.file, []byte("synthetic-private-output")); e != nil {
44 os.Exit(4)
45 }
46 output.Close()
47 os.Exit(0)
48 }
49 for _, mode := range []string{"valid", "stdout-alias", "stderr-alias", "stdin-alias", "input-alias", "read-only", "regular", "socket", "named-pipe"} {
50 t.Run(mode, func(t *testing.T) {
51 reader, writer, e := os.Pipe()
52 if e != nil {
53 t.Fatal(e)
54 }
55 defer reader.Close()
56 defer writer.Close()
57 inherited := writer
58 var extra *os.File
59 switch mode {
60 case "read-only":
61 inherited = reader
62 case "regular":
63 extra, e = os.CreateTemp(t.TempDir(), "private")
64 if e != nil {
65 t.Fatal(e)
66 }
67 defer extra.Close()
68 inherited = extra
69 case "socket":
70 pair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
71 if err != nil {
72 t.Fatal(err)
73 }
74 extra = os.NewFile(uintptr(pair[0]), "socket")
75 defer extra.Close()
76 defer unix.Close(pair[1])
77 inherited = extra
78 case "named-pipe":
79 path := t.TempDir() + "/pipe"
80 if e = unix.Mkfifo(path, 0600); e != nil {
81 t.Fatal(e)
82 }
83 holder, err := unix.Open(path, unix.O_RDWR|unix.O_NONBLOCK, 0)
84 if err != nil {
85 t.Fatal(err)
86 }
87 defer unix.Close(holder)
88 fd, err := unix.Open(path, unix.O_WRONLY|unix.O_NONBLOCK, 0)
89 if err != nil {
90 t.Fatal(err)
91 }
92 extra = os.NewFile(uintptr(fd), path)
93 defer extra.Close()
94 inherited = extra
95 }
96 cmd := exec.Command(os.Args[0], "-test.run=^TestPrivateRequestDescriptorsSubprocess$")
97 cmd.Env = append(os.Environ(), "OFICINA_DESCRIPTOR_CHILD="+mode)
98 cmd.ExtraFiles = []*os.File{inherited}
99 if mode == "input-alias" {
100 cmd.ExtraFiles = append(cmd.ExtraFiles, reader)
101 }
102 var public bytes.Buffer
103 cmd.Stdout = &public
104 cmd.Stderr = &public
105 if mode == "stdout-alias" {
106 cmd.Stdout = writer
107 }
108 if mode == "stderr-alias" {
109 cmd.Stderr = writer
110 }
111 if mode == "stdin-alias" {
112 cmd.Stdin = reader
113 }
114 if e = cmd.Start(); e != nil {
115 t.Fatal(e)
116 }
117 writer.Close()
118 private, e := io.ReadAll(reader)
119 if e != nil {
120 t.Fatal(e)
121 }
122 if e = cmd.Wait(); e != nil {
123 t.Fatalf("descriptor %s: %v %s", mode, e, public.Bytes())
124 }
125 if public.Len() != 0 {
126 t.Fatal("private output reached standard streams")
127 }
128 if mode == "valid" && !bytes.Equal(private, []byte("synthetic-private-output")) {
129 t.Fatal("private pipe output")
130 }
131 if mode != "valid" && len(private) != 0 {
132 t.Fatal("refused output wrote bytes")
133 }
134 })
135 }
136}
137func ownerOperationPlan() OperationPlan {
138 input := operator.OwnerInput{InputBase: operator.InputBase{Version: 1, Operation: "credential.owner-seed", OperationID: testToken(23)}, Directory: planFixture().Directory.Origin, TeamID: 1, TeamSQID: "Uk", Address: "native-prepare-private-83@example.invalid", ExistingOnly: true}
139 raw, _ := json.Marshal(input, json.Deterministic(true))
140 return OperationPlan{1, testToken(20), "credential", testToken(23), "credential.owner-seed", raw}
141}
142func TestNativePreparationPrivateReplayAndFailedOutput(t *testing.T) {
143 if os.Getenv("OFICINA_PREPARE_CHILD") == "1" {
144 args := []string{"provision", "prepare", "--state", os.Getenv("OFICINA_STATE_PATH"), "--floor-dir", os.Getenv("OFICINA_FLOOR_PATH"), "--password-fd", "3", "--plan-fd", "4", "--request-fd", "5"}
145 if e := Run(args, os.Stdout); e != nil {
146 os.Exit(3)
147 }
148 os.Exit(0)
149 }
150 state, floor := persistentDirectory(t), persistentDirectory(t)
151 password := []byte("synthetic-prepare-private-password")
152 if _, e := Initialize(state, floor, password); e != nil {
153 t.Fatal(e)
154 }
155 enrollment, _ := canonicalJSON(planFixture())
156 if _, e := EnrollPlan(state, floor, password, enrollment); e != nil {
157 t.Fatal(e)
158 }
159 p := ownerOperationPlan()
160 plan, _ := json.Marshal(p, json.Deterministic(true))
161 run := func(raw []byte, broken bool) ([]byte, []byte, error) {
162 pr, pw, e := os.Pipe()
163 if e != nil {
164 t.Fatal(e)
165 }
166 defer pr.Close()
167 ir, iw, e := os.Pipe()
168 if e != nil {
169 t.Fatal(e)
170 }
171 defer ir.Close()
172 rr, rw, e := os.Pipe()
173 if e != nil {
174 t.Fatal(e)
175 }
176 defer rr.Close()
177 if broken {
178 rr.Close()
179 }
180 cmd := exec.Command(os.Args[0], "-test.run=^TestNativePreparationPrivateReplayAndFailedOutput$")
181 cmd.Env = append(os.Environ(), "OFICINA_PREPARE_CHILD=1", "OFICINA_STATE_PATH="+state, "OFICINA_FLOOR_PATH="+floor)
182 cmd.ExtraFiles = []*os.File{pr, ir, rw}
183 var public, stderr bytes.Buffer
184 cmd.Stdout = &public
185 cmd.Stderr = &stderr
186 if e = cmd.Start(); e != nil {
187 t.Fatal(e)
188 }
189 pr.Close()
190 ir.Close()
191 rw.Close()
192 pw.Write(password)
193 pw.Close()
194 iw.Write(raw)
195 iw.Close()
196 var request []byte
197 if !broken {
198 request, e = io.ReadAll(rr)
199 if e != nil {
200 t.Fatal(e)
201 }
202 }
203 e = cmd.Wait()
204 for _, stream := range [][]byte{public.Bytes(), stderr.Bytes()} {
205 if bytes.Contains(stream, []byte("native-prepare-private-83@example.invalid")) || bytes.Contains(stream, password) || bytes.Contains(stream, []byte("signed_authorization")) {
206 t.Fatal("private request leaked")
207 }
208 }
209 return request, public.Bytes(), e
210 }
211 if _, public, e := run(plan, true); e == nil || len(public) != 0 {
212 t.Fatal("failed private output reported success", e)
213 }
214 j, e := existingJournal(state, floor, password)
215 if e != nil {
216 t.Fatal(e)
217 }
218 before := snapshot(t, j).installations[testToken(20)]
219 saved := append([]byte(nil), before.Operations[testToken(23)].Raw...)
220 if before.Operations[testToken(23)].Status != "prepared" || before.Heads["credential"].Sequence != 0 {
221 t.Fatal("output failure advanced state")
222 }
223 closeExisting(j)
224 request, public, e := run(plan, false)
225 if e != nil || !bytes.Equal(request, saved) {
226 t.Fatal("exact failed-output resume", e)
227 }
228 var info PreparationInfo
229 if json.Unmarshal(public, &info, json.RejectUnknownMembers(true)) != nil || info.State != "local-request" || info.Sequence != 1 {
230 t.Fatal("public preparation status")
231 }
232 again, _, e := run(plan, false)
233 if e != nil || !bytes.Equal(again, request) {
234 t.Fatal("cross-process replay changed bytes", e)
235 }
236 padded := append(append([]byte(nil), plan...), bytes.Repeat([]byte(" "), operator.BodyLimit-len(plan))...)
237 if len(padded) != operator.BodyLimit {
238 t.Fatal("test boundary")
239 }
240 exact, _, e := run(padded, false)
241 if e != nil || !bytes.Equal(exact, request) {
242 t.Fatal("64KiB local plan boundary", e)
243 }
244 if _, public, e = run(append(padded, ' '), false); e == nil || len(public) != 0 {
245 t.Fatal("oversized plan accepted", e)
246 }
247 wrong := p
248 wrong.Role = "directory"
249 raw, _ := json.Marshal(wrong)
250 if _, public, e = run(raw, false); e == nil || len(public) != 0 {
251 t.Fatal("foreign role accepted")
252 }
253 wrong = p
254 wrong.Installation = testToken(99)
255 raw, _ = json.Marshal(wrong)
256 if _, public, e = run(raw, false); e == nil || len(public) != 0 {
257 t.Fatal("foreign installation accepted")
258 }
259 j, e = existingJournal(state, floor, password)
260 if e != nil {
261 t.Fatal(e)
262 }
263 defer closeExisting(j)
264 after := snapshot(t, j).installations[testToken(20)]
265 if len(after.Operations) != 1 || after.Heads["credential"].Sequence != 0 || after.Operations[testToken(23)].Status != "prepared" {
266 t.Fatal("local request implied execution")
267 }
268}
269
270type shortPrivateWriter struct{}
271
272func (shortPrivateWriter) Write(p []byte) (int, error) { return len(p) - 1, nil }
273func TestPrivatePreparationBoundsAndCollectorRefusal(t *testing.T) {
274 for _, size := range []int{0, 1, operator.BodyLimit, operator.BodyLimit + 1} {
275 raw := bytes.Repeat([]byte("x"), size)
276 var out bytes.Buffer
277 e := writePrivateRequest(&out, raw)
278 if size == 0 || size > operator.BodyLimit {
279 if e != ErrRefused || out.Len() != 0 {
280 t.Fatal(size, e)
281 }
282 } else if e != nil || !bytes.Equal(raw, out.Bytes()) {
283 t.Fatal(size, e)
284 }
285 }
286 if e := writePrivateRequest(shortPrivateWriter{}, []byte("private")); e != ErrUnavailable {
287 t.Fatal("short output")
288 }
289 for _, action := range []string{"directory.verify-consumer", "directory.activate-consumer"} {
290 p := ownerOperationPlan()
291 p.Role = "directory"
292 p.Action = action
293 p.Input = jsontext.Value(`{}`)
294 raw, _ := json.Marshal(p)
295 if _, _, e := decodeOperationPlan(raw); e != ErrRefused {
296 t.Fatal("missing collector signed", action)
297 }
298 }
299 for _, fd := range []int{-1, 0, 1, 2, 99999} {
300 if out, e := privateRequestOutput(fd); out != nil || e != ErrRefused {
301 t.Fatal("unsafe descriptor", strconv.Itoa(fd), e)
302 }
303 }
304 p := ownerOperationPlan()
305 input := new(operator.OwnerInput)
306 if json.Unmarshal(p.Input, input) != nil {
307 t.Fatal("fixture")
308 }
309 input.ExistingOnly = false
310 if preparationScope(&installationState{Plan: planFixture()}, input) {
311 t.Fatal("unbacked person creation preparation")
312 }
313}
314
315func TestPreparationClosedPlanRefusesUnknownAndDuplicateFields(t *testing.T) {
316 p := ownerOperationPlan()
317 raw, _ := json.Marshal(p, json.Deterministic(true))
318 for _, bad := range [][]byte{
319 append([]byte{}, append(raw, raw...)...),
320 bytes.Replace(raw, []byte(`"version":1`), []byte(`"version":1,"version":1`), 1),
321 bytes.Replace(raw, []byte(`"version":1`), []byte(`"version":1,"unknown":"private@example.invalid"`), 1),
322 } {
323 if _, _, e := decodeOperationPlan(bad); e != ErrRefused {
324 t.Fatal("non-closed plan", e)
325 }
326 }
327}
328
329func TestFullPrivatePipeCancellationKeepsExactRequest(t *testing.T) {
330 j, p := stateFixture(t)
331 op := testToken(23)
332 raw, e := j.prepare(context.Background(), p.Installation, op, "directory.initialize", initialization(op))
333 if e != nil {
334 t.Fatal(e)
335 }
336 fds := make([]int, 2)
337 if e = unix.Pipe2(fds, unix.O_CLOEXEC|unix.O_NONBLOCK); e != nil {
338 t.Fatal(e)
339 }
340 defer unix.Close(fds[0])
341 output, e := privateRequestOutput(fds[1])
342 if e != nil {
343 unix.Close(fds[1])
344 t.Fatal(e)
345 }
346 defer output.Close()
347 fill := bytes.Repeat([]byte("x"), 4096)
348 filled := 0
349 for {
350 n, err := unix.Write(fds[1], fill)
351 filled += n
352 if err == unix.EAGAIN {
353 break
354 }
355 if err != nil {
356 t.Fatal(err)
357 }
358 }
359 if filled <= 0 {
360 t.Fatal("pipe was not filled")
361 }
362 ctx, cancel := context.WithCancel(context.Background())
363 defer cancel()
364 done := make(chan error, 1)
365 go func() { done <- output.write(ctx, raw) }()
366 select {
367 case e := <-done:
368 t.Fatal("full pipe did not block", e)
369 case <-time.After(50 * time.Millisecond):
370 }
371 cancel()
372 select {
373 case e := <-done:
374 if e != ErrUnavailable {
375 t.Fatal("cancellation error", e)
376 }
377 case <-time.After(2 * time.Second):
378 t.Fatal("cancellation did not interrupt pipe")
379 }
380 j.close()
381 reopened, e := openJournal(j.dir, j.floor, j.key, false)
382 if e != nil {
383 t.Fatal("process lock retained", e)
384 }
385 defer reopened.close()
386 repeated, e := reopened.prepare(context.Background(), p.Installation, op, "directory.initialize", initialization(op))
387 if e != nil || !bytes.Equal(raw, repeated) {
388 t.Fatal("interrupted output lost exact request", e)
389 }
390 s := snapshot(t, reopened).installations[p.Installation]
391 if s.Operations[op].Status != "prepared" || s.Heads["directory"].Sequence != 0 {
392 t.Fatal("cancellation implied execution")
393 }
394}
395
396func TestPrepareRequestCancelsFullPipeAndReleasesRegistry(t *testing.T) {
397 state, floor := persistentDirectory(t), persistentDirectory(t)
398 password := []byte("synthetic-full-pipe-password")
399 if _, e := Initialize(state, floor, password); e != nil {
400 t.Fatal(e)
401 }
402 enrollment, _ := canonicalJSON(planFixture())
403 if _, e := EnrollPlan(state, floor, password, enrollment); e != nil {
404 t.Fatal(e)
405 }
406 p := ownerOperationPlan()
407 plan, _ := json.Marshal(p, json.Deterministic(true))
408 fds := make([]int, 2)
409 if e := unix.Pipe2(fds, unix.O_CLOEXEC|unix.O_NONBLOCK); e != nil {
410 t.Fatal(e)
411 }
412 defer unix.Close(fds[0])
413 output, e := privateRequestOutput(fds[1])
414 if e != nil {
415 unix.Close(fds[1])
416 t.Fatal(e)
417 }
418 defer output.Close()
419 for {
420 _, e = unix.Write(fds[1], bytes.Repeat([]byte("x"), 4096))
421 if e == unix.EAGAIN {
422 break
423 }
424 if e != nil {
425 t.Fatal(e)
426 }
427 }
428 ctx, cancel := context.WithCancel(context.Background())
429 defer cancel()
430 done := make(chan error, 1)
431 go func() { _, e := prepareRequest(ctx, state, floor, password, plan, output); done <- e }()
432 // Observe the immutable fixture record without joining its writer lock.
433 observer, e := sql.Open("sqlite", "file:"+state+"/registry.db?mode=ro")
434 if e != nil {
435 t.Fatal(e)
436 }
437 defer observer.Close()
438 deadline := time.Now().Add(30 * time.Second)
439 for {
440 var n int
441 e = observer.QueryRow("SELECT count(*) FROM records WHERE operation=? AND state='finalized'", p.Operation).Scan(&n)
442 if e == nil && n == 1 {
443 break
444 }
445 select {
446 case e := <-done:
447 t.Fatal("preparation stopped before output", e)
448 default:
449 }
450 if time.Now().After(deadline) {
451 t.Fatal("fixture request was not persisted")
452 }
453 time.Sleep(10 * time.Millisecond)
454 }
455 select {
456 case e := <-done:
457 t.Fatal("full output did not wait", e)
458 case <-time.After(50 * time.Millisecond):
459 }
460 cancel()
461 select {
462 case e := <-done:
463 if e != ErrUnavailable {
464 t.Fatal("canceled preparation", e)
465 }
466 case <-time.After(2 * time.Second):
467 t.Fatal("prepareRequest ignored cancellation")
468 }
469 observer.Close()
470 j, e := existingJournal(state, floor, password)
471 if e != nil {
472 t.Fatal("registry lock not released", e)
473 }
474 defer closeExisting(j)
475 s := snapshot(t, j).installations[p.Installation]
476 saved := s.Operations[p.Operation]
477 if saved == nil || saved.Status != "prepared" || s.Heads[p.Role].Sequence != 0 {
478 t.Fatal("canceled output changed authority")
479 }
480 _, input, e := decodeOperationPlan(plan)
481 if e != nil {
482 t.Fatal(e)
483 }
484 replay, e := j.prepare(context.Background(), p.Installation, p.Operation, p.Action, input)
485 if e != nil || !bytes.Equal(replay, saved.Raw) {
486 t.Fatal("canceled prepare lost exact retry", e)
487 }
488}
489