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 "errors"
8 "os"
9 "path/filepath"
10 "strconv"
11 "strings"
12 "syscall"
13 "testing"
14
15 "github.com/carlosframework/platform/internal/hostbox"
16 "github.com/carlosframework/platform/internal/hostguard"
17 "github.com/carlosframework/platform/internal/stopevidence"
18)
19
20// The kernel half without root: the fixture /proc and cgroup tree stand in
21// for the real ones (the root-only checks wrap this).
22func TestVerifyWritersAtUsesRootsOwnObservation(t *testing.T) {
23 fx := newWritersKernelFixture(t)
24 if _, err := fx.verify(t); err != nil {
25 t.Fatal(err)
26 }
27 t.Run("a foreign holder", func(t *testing.T) {
28 fx := newWritersKernelFixture(t)
29 fx.holdDB(t, 30, 3)
30 if _, err := fx.verify(t); !errors.Is(err, stopevidence.ErrForeignHolder) {
31 t.Fatalf("got %v", err)
32 }
33 })
34 t.Run("the agent's unhinted descriptor", func(t *testing.T) {
35 fx := newWritersKernelFixture(t)
36 fx.hints = nil
37 if _, err := fx.verify(t); !errors.Is(err, stopevidence.ErrForeignHolder) {
38 t.Fatalf("got %v", err)
39 }
40 })
41 t.Run("a populated role cgroup", func(t *testing.T) {
42 fx := newWritersKernelFixture(t)
43 if err := os.WriteFile(filepath.Join(fx.cgroupRoot, fx.role, "cgroup.events"), []byte("populated 1\nfrozen 0\n"), 0o600); err != nil {
44 t.Fatal(err)
45 }
46 if _, err := fx.verify(t); !errors.Is(err, ErrClaimStopInventory) {
47 t.Fatalf("got %v", err)
48 }
49 })
50 t.Run("unsettled spawns", func(t *testing.T) {
51 fx := newWritersKernelFixture(t)
52 fx.journal.Settled = false
53 if _, err := fx.verify(t); !errors.Is(err, ErrClaimStopInventory) {
54 t.Fatalf("got %v", err)
55 }
56 })
57 t.Run("a pending role", func(t *testing.T) {
58 fx := newWritersKernelFixture(t)
59 fx.journal.PendingRoles = true
60 if _, err := fx.verify(t); !errors.Is(err, ErrClaimStopInventory) {
61 t.Fatalf("got %v", err)
62 }
63 })
64 t.Run("a replaced state directory", func(t *testing.T) {
65 fx := newWritersKernelFixture(t)
66 fx.inv.Entries[0].Inode++
67 if _, err := fx.verify(t); !errors.Is(err, ErrClaimStopInventory) {
68 t.Fatalf("got %v", err)
69 }
70 })
71}
72
73func TestVerifyNoProcessAt(t *testing.T) {
74 fx := newWritersKernelFixture(t)
75 fx.dropAgentHolds(t)
76 if _, err := fx.noProcess(t, false); err != nil {
77 t.Fatal(err)
78 }
79 t.Run("a holder of the database", func(t *testing.T) {
80 fx := newWritersKernelFixture(t)
81 if _, err := fx.noProcess(t, false); !errors.Is(err, ErrClaimStopIdentity) {
82 t.Fatalf("got %v", err)
83 }
84 })
85 t.Run("a launch journal entry for the host", func(t *testing.T) {
86 fx := newWritersKernelFixture(t)
87 fx.dropAgentHolds(t)
88 if _, err := fx.noProcess(t, true); !errors.Is(err, ErrClaimStopInventory) {
89 t.Fatalf("got %v", err)
90 }
91 })
92 t.Run("a populated host cgroup", func(t *testing.T) {
93 fx := newWritersKernelFixture(t)
94 fx.dropAgentHolds(t)
95 dir := filepath.Join(fx.agentCgroup, "carlos-exec@shop.example", "gen-1")
96 if err := os.MkdirAll(dir, 0o700); err != nil {
97 t.Fatal(err)
98 }
99 if err := os.WriteFile(filepath.Join(dir, "cgroup.events"), []byte("populated 1\n"), 0o600); err != nil {
100 t.Fatal(err)
101 }
102 if err := os.WriteFile(filepath.Join(filepath.Dir(dir), "cgroup.events"), []byte("populated 0\n"), 0o600); err != nil {
103 t.Fatal(err)
104 }
105 if _, err := fx.noProcess(t, false); !errors.Is(err, ErrClaimStopInventory) {
106 t.Fatalf("got %v", err)
107 }
108 })
109 t.Run("an empty host cgroup", func(t *testing.T) {
110 fx := newWritersKernelFixture(t)
111 fx.dropAgentHolds(t)
112 dir := filepath.Join(fx.agentCgroup, "carlos-exec@shop.example")
113 if err := os.MkdirAll(dir, 0o700); err != nil {
114 t.Fatal(err)
115 }
116 if err := os.WriteFile(filepath.Join(dir, "cgroup.events"), []byte("populated 0\n"), 0o600); err != nil {
117 t.Fatal(err)
118 }
119 ev, err := fx.noProcess(t, false)
120 if err != nil || len(ev.CgroupIdentities) != 2 || !strings.HasPrefix(ev.CgroupIdentities[0], "empty:") || !strings.HasPrefix(ev.CgroupIdentities[1], "absent:") {
121 t.Fatalf("%+v %v", ev, err)
122 }
123 })
124}
125
126type writersKernelFixture struct {
127 proc, cgroupRoot, dataRoot, agentCgroup, role string
128 db string
129 journal hostbox.StopKernelJournal
130 inv StopInventory
131 target hostguard.FenceTarget
132 peer StopPeer
133 hints []stopevidence.DescriptorHint
134}
135
136type fixedKernelJournal struct{ j hostbox.StopKernelJournal }
137
138func (f fixedKernelJournal) ReadStopKernelJournal(context.Context, hostguard.FenceTarget) (hostbox.StopKernelJournal, error) {
139 return f.j, nil
140}
141
142type fixedLaunchHosts bool
143
144func (f fixedLaunchHosts) HostLaunches(_ context.Context, host string) ([]hostbox.LaunchInventory, error) {
145 if !f {
146 return nil, nil
147 }
148 return []hostbox.LaunchInventory{{Target: hostguard.FenceTarget{Host: host, Incarnation: "inc-1", Stamp: 11}}}, nil
149}
150
151func fileID(t *testing.T, path string) (uint64, uint64) {
152 t.Helper()
153 fi, err := os.Stat(path)
154 if err != nil {
155 t.Fatal(err)
156 }
157 st := fi.Sys().(*syscall.Stat_t)
158 return uint64(st.Dev), st.Ino
159}
160
161func newWritersKernelFixture(t *testing.T) *writersKernelFixture {
162 t.Helper()
163 fx := &writersKernelFixture{proc: t.TempDir(), cgroupRoot: t.TempDir(), dataRoot: t.TempDir(), agentCgroup: t.TempDir(), role: "carlos/claim-1.local"}
164 fx.target = hostguard.FenceTarget{Host: "shop.example", Incarnation: "inc-1", Stamp: 11, Session: 13, Box: "box-1", Enrollment: "enrollment-1", BootID: "boot-1", ControllerID: "controller-1"}
165 fx.peer = StopPeer{UID: 1001, PID: 20, ProcessStart: "start-20", BootID: "boot-1", ControllerID: "controller-1"}
166 var groups []hostbox.StopCgroupIdentity
167 for _, rel := range []string{fx.role, fx.role + "/role-a"} {
168 dir := filepath.Join(fx.cgroupRoot, rel)
169 if err := os.MkdirAll(dir, 0o700); err != nil {
170 t.Fatal(err)
171 }
172 if err := os.WriteFile(filepath.Join(dir, "cgroup.events"), []byte("populated 0\nfrozen 0\n"), 0o600); err != nil {
173 t.Fatal(err)
174 }
175 dev, ino := fileID(t, dir)
176 groups = append(groups, hostbox.StopCgroupIdentity{RelativePath: rel, Device: dev, Inode: ino})
177 }
178 fx.journal = hostbox.StopKernelJournal{WritersClosed: true, V: 1, Target: fx.target, Settled: true, Cgroups: groups}
179 state := filepath.Join(fx.dataRoot, "shop.example")
180 if err := os.Mkdir(state, 0o700); err != nil {
181 t.Fatal(err)
182 }
183 fx.db = filepath.Join(state, "data.db")
184 for _, name := range []string{fx.db, fx.db + "-wal"} {
185 if err := os.WriteFile(name, nil, 0o600); err != nil {
186 t.Fatal(err)
187 }
188 }
189 sdev, sino := fileID(t, state)
190 fx.inv = StopInventory{Complete: true, Host: fx.target.Host, Incarnation: "inc-1", InstallationStamp: 11, Box: "box-1", Enrollment: "enrollment-1",
191 Entries: []StopInventoryEntry{{Kind: "path", ManagedRoot: "data", RelativePath: "shop.example", Identity: inventoryIdentityFixture(t, fx.dataRoot, "shop.example"), Type: syscall.S_IFDIR, Device: sdev, Inode: sino, Materialization: "materialized"}}}
192 fx.holdDB(t, 20, 7)
193 dev, ino := fileID(t, fx.db)
194 fx.hints = []stopevidence.DescriptorHint{{FD: 7, Device: dev, Inode: ino}}
195 return fx
196}
197
198func (fx *writersKernelFixture) holdDB(t *testing.T, pid, fd int) {
199 t.Helper()
200 dir := filepath.Join(fx.proc, strconv.Itoa(pid), "task", strconv.Itoa(pid), "fd")
201 if err := os.MkdirAll(dir, 0o700); err != nil {
202 t.Fatal(err)
203 }
204 if err := os.Symlink(fx.db, filepath.Join(dir, strconv.Itoa(fd))); err != nil {
205 t.Fatal(err)
206 }
207 stat := strconv.Itoa(pid) + " (p) S" + strings.Repeat(" 0", 18) + " start-" + strconv.Itoa(pid) + " 0\n"
208 if err := os.WriteFile(filepath.Join(fx.proc, strconv.Itoa(pid), "stat"), []byte(stat), 0o600); err != nil {
209 t.Fatal(err)
210 }
211}
212
213func (fx *writersKernelFixture) dropAgentHolds(t *testing.T) {
214 t.Helper()
215 if err := os.Remove(filepath.Join(fx.proc, "20", "task", "20", "fd", "7")); err != nil {
216 t.Fatal(err)
217 }
218}
219
220func (fx *writersKernelFixture) roots() stopKernelRoots {
221 return stopKernelRoots{Proc: fx.proc, Cgroup: fx.cgroupRoot, Managed: map[string]string{"data": fx.dataRoot}, AgentCgroup: fx.agentCgroup}
222}
223
224func (fx *writersKernelFixture) verify(t *testing.T) (WritersKernelEvidence, error) {
225 return verifyWritersAt(t.Context(), fx.roots(), fixedKernelJournal{fx.journal}, fx.target, fx.inv, fx.peer, fx.hints)
226}
227
228func (fx *writersKernelFixture) noProcess(t *testing.T, has bool) (StopKernelEvidence, error) {
229 target := fx.target
230 target.Session = 0
231 return verifyNoProcessAt(t.Context(), fx.roots(), fixedLaunchHosts(has), target, fx.inv)
232}
233
234var _ = errors.Is
235