| 1 | //go:build linux |
| 2 | |
| 3 | package hostbox |
| 4 | |
| 5 | import ( |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "testing" |
| 9 | |
| 10 | "github.com/carlosframework/platform/internal/hostguard" |
| 11 | "golang.org/x/sys/unix" |
| 12 | ) |
| 13 | |
| 14 | func TestLaunchIntentionRecoveryRefusesForeignChildren(t *testing.T) { |
| 15 | for _, mode := range []string{"absent", "empty", "populated", "descendant", "symlink", "parent-replaced", "reboot-absent-parent", "reboot-absent-ancestor", "reboot-reused-parent"} { |
| 16 | t.Run(mode, func(t *testing.T) { |
| 17 | ctx := t.Context() |
| 18 | root := t.TempDir() |
| 19 | parentPath := filepath.Join(root, "delegate", "parent") |
| 20 | if err := os.MkdirAll(parentPath, 0700); err != nil { |
| 21 | t.Fatal(err) |
| 22 | } |
| 23 | var st unix.Stat_t |
| 24 | if err := unix.Stat(parentPath, &st); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | parent := LaunchGroup{RelativePath: "delegate/parent", Device: uint64(st.Dev), Inode: st.Ino} |
| 28 | target := hostguard.FenceTarget{Host: "app.test", Incarnation: "inc", Stamp: 1, Session: 1, Box: "box", Enrollment: "enroll", BootID: "boot", ControllerID: "ctrl"} |
| 29 | j, err := OpenLaunchJournal(filepath.Join(t.TempDir(), "launch.db")) |
| 30 | if err != nil { |
| 31 | t.Fatal(err) |
| 32 | } |
| 33 | defer j.Close() |
| 34 | if err := j.Prelaunch(ctx, LaunchInventory{Target: target, ControllerPID: 42, ProcessStart: "start", Groups: []LaunchGroup{parent}, NoState: true}); err != nil { |
| 35 | t.Fatal(err) |
| 36 | } |
| 37 | r := LaunchReservation{Target: target, ID: "first:main", Role: "main", InvocationID: "first", Inputs: "inputs", Parent: parent, Group: LaunchGroup{RelativePath: "delegate/parent/role-first", InvocationID: "first"}, Executable: LaunchFile{Device: 1, Inode: 2}} |
| 38 | if err := j.IntendRole(ctx, r); err != nil { |
| 39 | t.Fatal(err) |
| 40 | } |
| 41 | childPath := filepath.Join(root, r.Group.RelativePath) |
| 42 | reboot := false |
| 43 | switch mode { |
| 44 | case "reboot-absent-ancestor": |
| 45 | reboot = true |
| 46 | if err := os.RemoveAll(filepath.Join(root, "delegate")); err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | case "reboot-absent-parent": |
| 50 | reboot = true |
| 51 | if err := os.Remove(parentPath); err != nil { |
| 52 | t.Fatal(err) |
| 53 | } |
| 54 | case "reboot-reused-parent": |
| 55 | reboot = true |
| 56 | case "parent-replaced": |
| 57 | if err := os.Rename(parentPath, parentPath+".old"); err != nil { |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | if err := os.Mkdir(parentPath, 0700); err != nil { |
| 61 | t.Fatal(err) |
| 62 | } |
| 63 | case "symlink": |
| 64 | if err := os.Symlink(t.TempDir(), childPath); err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | case "empty", "populated", "descendant": |
| 68 | if err := os.Mkdir(childPath, 0700); err != nil { |
| 69 | t.Fatal(err) |
| 70 | } |
| 71 | value := "populated 0\n" |
| 72 | if mode == "populated" { |
| 73 | value = "populated 1\n" |
| 74 | } |
| 75 | if err := os.WriteFile(filepath.Join(childPath, "cgroup.events"), []byte(value), 0600); err != nil { |
| 76 | t.Fatal(err) |
| 77 | } |
| 78 | if mode == "descendant" { |
| 79 | if err := os.Mkdir(filepath.Join(childPath, "unknown"), 0700); err != nil { |
| 80 | t.Fatal(err) |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | err = j.ResolveRoleIntentions(ctx, target, root, reboot) |
| 85 | accepted := mode == "absent" || mode == "empty" || mode == "reboot-absent-parent" || mode == "reboot-absent-ancestor" |
| 86 | if (err == nil) != accepted { |
| 87 | t.Fatalf("accepted=%v err=%v", accepted, err) |
| 88 | } |
| 89 | in, readErr := j.Read(ctx, target) |
| 90 | if readErr != nil { |
| 91 | t.Fatal(readErr) |
| 92 | } |
| 93 | want := "intended" |
| 94 | if accepted { |
| 95 | want = "settled" |
| 96 | } |
| 97 | if in.Reservations[0].State != want { |
| 98 | t.Fatalf("changed refused intention: %+v", in) |
| 99 | } |
| 100 | if accepted { |
| 101 | if err := j.ResolveRoleIntentions(ctx, target, root, reboot); err != nil { |
| 102 | t.Fatal("replay", err) |
| 103 | } |
| 104 | } |
| 105 | }) |
| 106 | } |
| 107 | } |
| 108 | |