carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "fmt"
8 "github.com/carlosframework/platform/internal/hostbox"
9 "github.com/carlosframework/platform/internal/hostguard"
10 "os"
11 "path/filepath"
12 "strings"
13 "testing"
14)
15
16func TestClaimKernelFindsDeletedDescriptor(t *testing.T) {
17 d := t.TempDir()
18 path := filepath.Join(d, "data.db")
19 f, err := os.Create(path)
20 if err != nil {
21 t.Fatal(err)
22 }
23 defer f.Close()
24 id, err := stopIdentity(path)
25 if err != nil {
26 t.Fatal(err)
27 }
28 if err := os.Remove(path); err != nil {
29 t.Fatal(err)
30 }
31 proc := filepath.Join(d, "proc")
32 if err := os.MkdirAll(filepath.Join(proc, "42", "fd"), 0700); err != nil {
33 t.Fatal(err)
34 }
35 if err := os.WriteFile(filepath.Join(proc, "42", "stat"), []byte("42 (fixture) S "+strings.Repeat("0 ", 18)+"123 0"), 0600); err != nil {
36 t.Fatal(err)
37 }
38 if err := os.Symlink(fmt.Sprintf("/proc/self/fd/%d", f.Fd()), filepath.Join(proc, "42", "fd", "3")); err != nil {
39 t.Fatal(err)
40 }
41 if err := scanStopDescriptors(context.Background(), proc, []hostbox.StopFileIdentity{id}); err == nil || !strings.Contains(err.Error(), "database descriptor remains") {
42 t.Fatalf("deleted descriptor evidence = %v", err)
43 }
44}
45func TestClaimKernelRejectsIncompleteTree(t *testing.T) {
46 d := t.TempDir()
47 root := filepath.Join(d, "host")
48 if err := os.Mkdir(root, 0700); err != nil {
49 t.Fatal(err)
50 }
51 if err := os.WriteFile(filepath.Join(root, "cgroup.events"), []byte("populated 0\nfrozen 0\n"), 0600); err != nil {
52 t.Fatal(err)
53 }
54 id, err := stopIdentity(root)
55 if err != nil {
56 t.Fatal(err)
57 }
58 groups := []hostbox.StopCgroupIdentity{{RelativePath: "host", Device: id.Device, Inode: id.Inode}}
59 if _, err := scanStopCgroups(context.Background(), d, groups); err != nil {
60 t.Fatal(err)
61 }
62 if err := os.Mkdir(filepath.Join(root, "unknown"), 0700); err != nil {
63 t.Fatal(err)
64 }
65 if _, err := scanStopCgroups(context.Background(), d, groups); err == nil {
66 t.Fatal("unknown child accepted")
67 }
68}
69
70func TestClaimKernelFindsUnrecordedWALPath(t *testing.T) {
71 proc := t.TempDir()
72 if err := os.MkdirAll(filepath.Join(proc, "42", "fd"), 0700); err != nil {
73 t.Fatal(err)
74 }
75 if err := os.WriteFile(filepath.Join(proc, "42", "stat"), []byte("42 (fixture) S "+strings.Repeat("0 ", 18)+"123 0"), 0600); err != nil {
76 t.Fatal(err)
77 }
78 if err := os.Symlink("/owned/data.db-wal (deleted)", filepath.Join(proc, "42", "fd", "3")); err != nil {
79 t.Fatal(err)
80 }
81 if err := scanStopDescriptors(context.Background(), proc, nil, []string{"/owned/data.db-wal"}); err == nil || !strings.Contains(err.Error(), "managed state descriptor remains") {
82 t.Fatalf("unrecorded WAL = %v", err)
83 }
84}
85
86type rebootJournalFixture struct{ j hostbox.StopKernelJournal }
87
88func (j *rebootJournalFixture) ReadStopKernelJournal(context.Context, hostguard.FenceTarget) (hostbox.StopKernelJournal, error) {
89 return j.j, nil
90}
91func (j *rebootJournalFixture) SettleRecovered(context.Context, hostguard.FenceTarget) error {
92 j.j.Settled = true
93 return nil
94}
95func TestClaimKernelPositiveRebootReconstructsEvidence(t *testing.T) {
96 root, proc := t.TempDir(), t.TempDir()
97 if err := os.MkdirAll(filepath.Join(proc, "sys/kernel/random"), 0700); err != nil {
98 t.Fatal(err)
99 }
100 if err := os.WriteFile(filepath.Join(proc, "sys/kernel/random/boot_id"), []byte("new-boot\n"), 0600); err != nil {
101 t.Fatal(err)
102 }
103 target := hostguard.FenceTarget{Host: "shop.example", Incarnation: "inc", Stamp: 11, Session: 13, Box: "box", Enrollment: "enroll", BootID: "old-boot", ControllerID: "old-controller", FenceID: "fence"}
104 parent, err := claimContainmentParent("/sys/fs/cgroup/system.slice/carlos-edge.service", target)
105 if err != nil {
106 t.Fatal(err)
107 }
108 journal := &rebootJournalFixture{j: hostbox.StopKernelJournal{V: 1, Target: target, ControllerPID: 42, ProcessStart: "old-start", Cgroups: []hostbox.StopCgroupIdentity{{RelativePath: parent, Device: 1, Inode: 2}, {RelativePath: parent + "/role-main", Device: 1, Inode: 3, InvocationID: "main"}}}}
109 kernel := LinuxStopKernel{AgentCgroup: func(context.Context) (string, error) { return "/sys/fs/cgroup/system.slice/carlos-edge.service", nil }, Journal: journal, CgroupRoot: root, ProcRoot: proc, ManagedRoots: map[string]string{"data": t.TempDir()}}
110 inventory := StopInventory{Complete: true, Host: target.Host, Incarnation: target.Incarnation, InstallationStamp: target.Stamp}
111 evidence, ack, err := kernel.recoverAfterReboot(t.Context(), target, inventory, journal.j, "new-boot", journal)
112 if err != nil {
113 t.Fatal(err)
114 }
115 if evidence.ObservedBootID != "new-boot" || ack.ControllerID != "old-controller" || !journal.j.Settled || len(evidence.CgroupIdentities) != 2 {
116 t.Fatalf("bad reconstruction: %+v %+v", evidence, ack)
117 }
118 if _, _, err := kernel.recoverAfterReboot(t.Context(), target, inventory, journal.j, "old-boot", journal); err == nil {
119 t.Fatal("same-boot absence accepted as reboot")
120 }
121 if _, err := scanStopCgroups(t.Context(), root, journal.j.Cgroups); err == nil {
122 t.Fatal("same-boot missing containment accepted")
123 }
124 if err := os.MkdirAll(filepath.Join(root, parent), 0700); err != nil {
125 t.Fatal(err)
126 }
127 if _, _, err := kernel.recoverAfterReboot(t.Context(), target, inventory, journal.j, "new-boot", journal); err == nil {
128 t.Fatal("reused group path accepted")
129 }
130}
131