| new file mode 100644 |
| index 0000000..06ca17f |
| --- /dev/null |
| +++ b/internal/provisioner/prepare_linux_test.go |
| @@ -0,0 +1,324 @@ |
| +//go:build linux |
| + |
| +package provisioner |
| + |
| +import ( |
| + "bytes" |
| + "io" |
| + "os" |
| + "os/exec" |
| + "strconv" |
| + "testing" |
| + |
| + "amadan.net/oficina/home/internal/operator" |
| + "github.com/go-json-experiment/json" |
| + "github.com/go-json-experiment/json/jsontext" |
| + "golang.org/x/sys/unix" |
| +) |
| + |
| +func TestPrivateRequestDescriptorsSubprocess(t *testing.T) { |
| + if mode := os.Getenv("OFICINA_DESCRIPTOR_CHILD"); mode != "" { |
| + var output *requestOutput |
| + var e error |
| + if mode == "input-alias" { |
| + output, e = privateRequestOutput(3, 4) |
| + } else { |
| + output, e = privateRequestOutput(3) |
| + } |
| + if mode != "valid" { |
| + if output != nil { |
| + output.Close() |
| + } |
| + if e != ErrRefused { |
| + os.Exit(2) |
| + } |
| + os.Exit(0) |
| + } |
| + if e != nil { |
| + os.Exit(3) |
| + } |
| + if e = writePrivateRequest(output.file, []byte("synthetic-private-output")); e != nil { |
| + os.Exit(4) |
| + } |
| + output.Close() |
| + os.Exit(0) |
| + } |
| + for _, mode := range []string{"valid", "stdout-alias", "stderr-alias", "stdin-alias", "input-alias", "read-only", "regular", "socket", "named-pipe"} { |
| + t.Run(mode, func(t *testing.T) { |
| + reader, writer, e := os.Pipe() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer reader.Close() |
| + defer writer.Close() |
| + inherited := writer |
| + var extra *os.File |
| + switch mode { |
| + case "read-only": |
| + inherited = reader |
| + case "regular": |
| + extra, e = os.CreateTemp(t.TempDir(), "private") |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer extra.Close() |
| + inherited = extra |
| + case "socket": |
| + pair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + extra = os.NewFile(uintptr(pair[0]), "socket") |
| + defer extra.Close() |
| + defer unix.Close(pair[1]) |
| + inherited = extra |
| + case "named-pipe": |
| + path := t.TempDir() + "/pipe" |
| + if e = unix.Mkfifo(path, 0600); e != nil { |
| + t.Fatal(e) |
| + } |
| + holder, err := unix.Open(path, unix.O_RDWR|unix.O_NONBLOCK, 0) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer unix.Close(holder) |
| + fd, err := unix.Open(path, unix.O_WRONLY|unix.O_NONBLOCK, 0) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + extra = os.NewFile(uintptr(fd), path) |
| + defer extra.Close() |
| + inherited = extra |
| + } |
| + cmd := exec.Command(os.Args[0], "-test.run=^TestPrivateRequestDescriptorsSubprocess$") |
| + cmd.Env = append(os.Environ(), "OFICINA_DESCRIPTOR_CHILD="+mode) |
| + cmd.ExtraFiles = []*os.File{inherited} |
| + if mode == "input-alias" { |
| + cmd.ExtraFiles = append(cmd.ExtraFiles, reader) |
| + } |
| + var public bytes.Buffer |
| + cmd.Stdout = &public |
| + cmd.Stderr = &public |
| + if mode == "stdout-alias" { |
| + cmd.Stdout = writer |
| + } |
| + if mode == "stderr-alias" { |
| + cmd.Stderr = writer |
| + } |
| + if mode == "stdin-alias" { |
| + cmd.Stdin = reader |
| + } |
| + if e = cmd.Start(); e != nil { |
| + t.Fatal(e) |
| + } |
| + writer.Close() |
| + private, e := io.ReadAll(reader) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + if e = cmd.Wait(); e != nil { |
| + t.Fatalf("descriptor %s: %v %s", mode, e, public.Bytes()) |
| + } |
| + if public.Len() != 0 { |
| + t.Fatal("private output reached standard streams") |
| + } |
| + if mode == "valid" && !bytes.Equal(private, []byte("synthetic-private-output")) { |
| + t.Fatal("private pipe output") |
| + } |
| + if mode != "valid" && len(private) != 0 { |
| + t.Fatal("refused output wrote bytes") |
| + } |
| + }) |
| + } |
| +} |
| +func ownerOperationPlan() OperationPlan { |
| + 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} |
| + raw, _ := json.Marshal(input, json.Deterministic(true)) |
| + return OperationPlan{1, testToken(20), "credential", testToken(23), "credential.owner-seed", raw} |
| +} |
| +func TestNativePreparationPrivateReplayAndFailedOutput(t *testing.T) { |
| + if os.Getenv("OFICINA_PREPARE_CHILD") == "1" { |
| + 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"} |
| + if e := Run(args, os.Stdout); e != nil { |
| + os.Exit(3) |
| + } |
| + os.Exit(0) |
| + } |
| + state, floor := persistentDirectory(t), persistentDirectory(t) |
| + password := []byte("synthetic-prepare-private-password") |
| + if _, e := Initialize(state, floor, password); e != nil { |
| + t.Fatal(e) |
| + } |
| + enrollment, _ := canonicalJSON(planFixture()) |
| + if _, e := EnrollPlan(state, floor, password, enrollment); e != nil { |
| + t.Fatal(e) |
| + } |
| + p := ownerOperationPlan() |
| + plan, _ := json.Marshal(p, json.Deterministic(true)) |
| + run := func(raw []byte, broken bool) ([]byte, []byte, error) { |
| + pr, pw, e := os.Pipe() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer pr.Close() |
| + ir, iw, e := os.Pipe() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer ir.Close() |
| + rr, rw, e := os.Pipe() |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer rr.Close() |
| + if broken { |
| + rr.Close() |
| + } |
| + cmd := exec.Command(os.Args[0], "-test.run=^TestNativePreparationPrivateReplayAndFailedOutput$") |
| + cmd.Env = append(os.Environ(), "OFICINA_PREPARE_CHILD=1", "OFICINA_STATE_PATH="+state, "OFICINA_FLOOR_PATH="+floor) |
| + cmd.ExtraFiles = []*os.File{pr, ir, rw} |
| + var public, stderr bytes.Buffer |
| + cmd.Stdout = &public |
| + cmd.Stderr = &stderr |
| + if e = cmd.Start(); e != nil { |
| + t.Fatal(e) |
| + } |
| + pr.Close() |
| + ir.Close() |
| + rw.Close() |
| + pw.Write(password) |
| + pw.Close() |
| + iw.Write(raw) |
| + iw.Close() |
| + var request []byte |
| + if !broken { |
| + request, e = io.ReadAll(rr) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + } |
| + e = cmd.Wait() |
| + for _, stream := range [][]byte{public.Bytes(), stderr.Bytes()} { |
| + if bytes.Contains(stream, []byte("native-prepare-private-83@example.invalid")) || bytes.Contains(stream, password) || bytes.Contains(stream, []byte("signed_authorization")) { |
| + t.Fatal("private request leaked") |
| + } |
| + } |
| + return request, public.Bytes(), e |
| + } |
| + if _, public, e := run(plan, true); e == nil || len(public) != 0 { |
| + t.Fatal("failed private output reported success", e) |
| + } |
| + j, e := existingJournal(state, floor, password) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + before := snapshot(t, j).installations[testToken(20)] |
| + saved := append([]byte(nil), before.Operations[testToken(23)].Raw...) |
| + if before.Operations[testToken(23)].Status != "prepared" || before.Heads["credential"].Sequence != 0 { |
| + t.Fatal("output failure advanced state") |
| + } |
| + closeExisting(j) |
| + request, public, e := run(plan, false) |
| + if e != nil || !bytes.Equal(request, saved) { |
| + t.Fatal("exact failed-output resume", e) |
| + } |
| + var info PreparationInfo |
| + if json.Unmarshal(public, &info, json.RejectUnknownMembers(true)) != nil || info.State != "local-request" || info.Sequence != 1 { |
| + t.Fatal("public preparation status") |
| + } |
| + again, _, e := run(plan, false) |
| + if e != nil || !bytes.Equal(again, request) { |
| + t.Fatal("cross-process replay changed bytes", e) |
| + } |
| + padded := append(append([]byte(nil), plan...), bytes.Repeat([]byte(" "), operator.BodyLimit-len(plan))...) |
| + if len(padded) != operator.BodyLimit { |
| + t.Fatal("test boundary") |
| + } |
| + exact, _, e := run(padded, false) |
| + if e != nil || !bytes.Equal(exact, request) { |
| + t.Fatal("64KiB local plan boundary", e) |
| + } |
| + if _, public, e = run(append(padded, ' '), false); e == nil || len(public) != 0 { |
| + t.Fatal("oversized plan accepted", e) |
| + } |
| + wrong := p |
| + wrong.Role = "directory" |
| + raw, _ := json.Marshal(wrong) |
| + if _, public, e = run(raw, false); e == nil || len(public) != 0 { |
| + t.Fatal("foreign role accepted") |
| + } |
| + wrong = p |
| + wrong.Installation = testToken(99) |
| + raw, _ = json.Marshal(wrong) |
| + if _, public, e = run(raw, false); e == nil || len(public) != 0 { |
| + t.Fatal("foreign installation accepted") |
| + } |
| + j, e = existingJournal(state, floor, password) |
| + if e != nil { |
| + t.Fatal(e) |
| + } |
| + defer closeExisting(j) |
| + after := snapshot(t, j).installations[testToken(20)] |
| + if len(after.Operations) != 1 || after.Heads["credential"].Sequence != 0 || after.Operations[testToken(23)].Status != "prepared" { |
| + t.Fatal("local request implied execution") |
| + } |
| +} |
| + |
| +type shortPrivateWriter struct{} |
| + |
| +func (shortPrivateWriter) Write(p []byte) (int, error) { return len(p) - 1, nil } |
| +func TestPrivatePreparationBoundsAndCollectorRefusal(t *testing.T) { |
| + for _, size := range []int{0, 1, operator.BodyLimit, operator.BodyLimit + 1} { |
| + raw := bytes.Repeat([]byte("x"), size) |
| + var out bytes.Buffer |
| + e := writePrivateRequest(&out, raw) |
| + if size == 0 || size > operator.BodyLimit { |
| + if e != ErrRefused || out.Len() != 0 { |
| + t.Fatal(size, e) |
| + } |
| + } else if e != nil || !bytes.Equal(raw, out.Bytes()) { |
| + t.Fatal(size, e) |
| + } |
| + } |
| + if e := writePrivateRequest(shortPrivateWriter{}, []byte("private")); e != ErrUnavailable { |
| + t.Fatal("short output") |
| + } |
| + for _, action := range []string{"directory.verify-consumer", "directory.activate-consumer"} { |
| + p := ownerOperationPlan() |
| + p.Role = "directory" |
| + p.Action = action |
| + p.Input = jsontext.Value(`{}`) |
| + raw, _ := json.Marshal(p) |
| + if _, _, e := decodeOperationPlan(raw); e != ErrRefused { |
| + t.Fatal("missing collector signed", action) |
| + } |
| + } |
| + for _, fd := range []int{-1, 0, 1, 2, 99999} { |
| + if out, e := privateRequestOutput(fd); out != nil || e != ErrRefused { |
| + t.Fatal("unsafe descriptor", strconv.Itoa(fd), e) |
| + } |
| + } |
| + p := ownerOperationPlan() |
| + input := new(operator.OwnerInput) |
| + if json.Unmarshal(p.Input, input) != nil { |
| + t.Fatal("fixture") |
| + } |
| + input.ExistingOnly = false |
| + if preparationScope(&installationState{Plan: planFixture()}, input) { |
| + t.Fatal("unbacked person creation preparation") |
| + } |
| +} |
| + |
| +func TestPreparationClosedPlanRefusesUnknownAndDuplicateFields(t *testing.T) { |
| + p := ownerOperationPlan() |
| + raw, _ := json.Marshal(p, json.Deterministic(true)) |
| + for _, bad := range [][]byte{ |
| + append([]byte{}, append(raw, raw...)...), |
| + bytes.Replace(raw, []byte(`"version":1`), []byte(`"version":1,"version":1`), 1), |
| + bytes.Replace(raw, []byte(`"version":1`), []byte(`"version":1,"unknown":"private@example.invalid"`), 1), |
| + } { |
| + if _, _, e := decodeOperationPlan(bad); e != ErrRefused { |
| + t.Fatal("non-closed plan", e) |
| + } |
| + } |
| +} |