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 && rootrunner
2
3package host
4
5import (
6 "encoding/json"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "reflect"
11 "testing"
12
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/hostguard"
15 "github.com/carlosframework/platform/internal/provision"
16 "github.com/carlosframework/platform/internal/rootstate"
17 "golang.org/x/sys/unix"
18)
19
20func TestPlan14GenerationPayloadPhysicalRootRunner(t *testing.T) {
21 const childKey = "PLAN14_E28_PAYLOAD_CHILD"
22 dir := os.Getenv(childKey)
23 if dir == "" {
24 dir = generationRootRunnerDir(t)
25 cmd := exec.CommandContext(t.Context(), "unshare", "--mount", "--pid", "--fork", "--mount-proc", os.Args[0], "-test.run=^TestPlan14GenerationPayloadPhysicalRootRunner$", "-test.v", "-test.timeout=2m")
26 cmd.Dir = dir
27 cmd.Env = append(os.Environ(), childKey+"="+dir)
28 if out, err := cmd.CombinedOutput(); err != nil {
29 t.Fatalf("isolated payload census: %v\n%s", err, out)
30 } else {
31 t.Logf("%s", out)
32 }
33 return
34 }
35 if os.Geteuid() != 0 {
36 t.Fatal("designated runner must execute as root")
37 }
38 t.Setenv("CARLOS_OPT_DIR", dir)
39 t.Setenv("CARLOS_DATA_DIR", filepath.Join(dir, "data"))
40 for _, path := range []string{filepath.Join(dir, "versions"), dataDir()} {
41 if err := os.Mkdir(path, 0755); err != nil {
42 t.Fatal(err)
43 }
44 }
45 pins, certificate := generationNamespacePinFixture(t, dir)
46 path := filepath.Join(dataDir(), "retained")
47 if err := os.WriteFile(path, []byte("predecessor payload"), 0600); err != nil {
48 t.Fatal(err)
49 }
50 var st unix.Stat_t
51 if err := unix.Stat(path, &st); err != nil {
52 t.Fatal(err)
53 }
54 // An empty current installation still carries its predecessor's payload.
55 // These are root-written fixtures, not migration or rebind authorization.
56 in := hostguard.RootInstallation{Host: "retained.example", Incarnation: "inc", Stamp: 5, Account: "bab", App: "shop", Box: "runner", Enrollment: "en", Migration: 1, InventoryRevision: 1, Empty: true}
57 in.Record = provision.RecordKey(in.Account, in.App, in.Host)
58 fences, err := rootstate.Open(filepath.Join(dir, "fences"), false, false)
59 if err != nil {
60 t.Fatal(err)
61 }
62 raw, err := json.Marshal(map[string]any{
63 "Schema": 1, "Installation": in, "Retired": []hostguard.Captured{{Incarnation: in.Incarnation, Stamp: 2}},
64 "State": map[string]any{"v": 1, "host": in.Host, "incarnation": in.Incarnation, "active_stamp": 5, "retired_stamps": []uint64{2}},
65 })
66 if err == nil {
67 err = fences.Write(in.Host+".json", raw)
68 }
69 fences.Close()
70 if err != nil {
71 t.Fatal(err)
72 }
73 journalPath := filepath.Join(dir, "journals.db")
74 journal, err := hostbox.OpenLaunchJournal(journalPath)
75 if err != nil {
76 t.Fatal(err)
77 }
78 service, err := hostbox.NewJournalService(journal)
79 if err != nil {
80 journal.Close()
81 t.Fatal(err)
82 }
83 for _, stamp := range []uint64{2, 5} {
84 payload := hostbox.StopInventory{Complete: true, Host: in.Host, Incarnation: in.Incarnation, InstallationStamp: stamp, Box: in.Box, Enrollment: in.Enrollment, DirectoryRevision: "1"}
85 if stamp == 2 {
86 payload.Entries = []hostbox.StopInventoryEntry{{Identity: inventoryIdentityFixture(t, dataDir(), "retained"), Kind: "path", ArtifactID: "retained", ManagedRoot: "data", RelativePath: "retained", Ownership: "owner", Materialization: "materialized", Device: uint64(st.Dev), Inode: st.Ino, Type: unix.S_IFREG}}
87 }
88 if err := service.Inventory.WriteStopInventory(t.Context(), payload); err != nil {
89 journal.Close()
90 t.Fatal(err)
91 }
92 }
93 journal.Close()
94 read := func() generationInventory {
95 t.Helper()
96 v, err := generationInventoryAt(t.Context(), filepath.Join(dir, "census"), filepath.Join(dir, "fences"), journalPath)
97 if err != nil {
98 t.Fatal(err)
99 }
100 return v
101 }
102 before := read()
103 accept := func() {
104 t.Helper()
105 if got, err := pins.StagedPins(t.Context(), dir); err != nil || len(got) != 0 || !certificate().Complete {
106 t.Fatalf("exact retained payload: %v %v", got, err)
107 }
108 }
109 accept()
110 for _, attack := range []string{"missing", "replacement", "symlink", "bind"} {
111 t.Run(attack, func(t *testing.T) {
112 initial := certificate()
113 if attack == "bind" {
114 other := filepath.Join(dataDir(), "other")
115 if err := os.WriteFile(other, []byte("different inode"), 0600); err != nil {
116 t.Fatal(err)
117 }
118 if err := unix.Mount(other, path, "", unix.MS_BIND, ""); err != nil {
119 t.Fatal(err)
120 }
121 t.Cleanup(func() {
122 if err := unix.Unmount(path, 0); err != nil {
123 t.Error(err)
124 }
125 })
126 } else {
127 if err := os.Rename(path, path+".old"); err != nil {
128 t.Fatal(err)
129 }
130 t.Cleanup(func() {
131 if attack != "missing" {
132 if err := os.Remove(path); err != nil {
133 t.Error(err)
134 }
135 }
136 if err := os.Rename(path+".old", path); err != nil {
137 t.Error(err)
138 }
139 })
140 if attack == "replacement" {
141 err = os.WriteFile(path, []byte("replacement"), 0600)
142 } else if attack == "symlink" {
143 err = os.Symlink(path+".old", path)
144 }
145 if err != nil {
146 t.Fatal(err)
147 }
148 }
149 if got, err := pins.StagedPins(t.Context(), dir); err == nil || got != nil {
150 t.Fatalf("changed predecessor payload certified: %v %v", got, err)
151 }
152 if c := certificate(); c.Complete || c.Revision <= initial.Revision {
153 t.Fatal("stale completeness survived physical refusal")
154 }
155 if !reflect.DeepEqual(before, read()) {
156 t.Fatal("physical refusal changed retained authority")
157 }
158 })
159 denied := certificate()
160 accept()
161 if certificate().Revision <= denied.Revision {
162 t.Fatal("restored payload reused an old certificate")
163 }
164 }
165 for _, kind := range []string{"route", "timer"} {
166 t.Run("unfinished-"+kind, func(t *testing.T) {
167 journal, err := hostbox.OpenLaunchJournal(journalPath)
168 if err != nil {
169 t.Fatal(err)
170 }
171 defer journal.Close()
172 service, err := hostbox.NewJournalService(journal)
173 if err != nil {
174 t.Fatal(err)
175 }
176 payload := before.Payloads[0].Inventory
177 if payload.InstallationStamp != 2 {
178 t.Fatal("expected retained predecessor")
179 }
180 entry := hostbox.StopInventoryEntry{Kind: kind, ArtifactID: "registry-payload", ManagedRoot: "registry", RelativePath: in.Host, RowIdentity: "row", Ownership: "owner", Materialization: "materialized"}
181 if kind == "timer" {
182 entry.RelativePath += "/backup"
183 }
184 payload.Entries = append(append([]hostbox.StopInventoryEntry(nil), payload.Entries...), entry)
185 write := func() {
186 t.Helper()
187 if err := service.Inventory.WriteStopInventory(t.Context(), payload); err != nil {
188 t.Fatal(err)
189 }
190 }
191 write()
192 accept()
193 initial := certificate()
194 payload.Entries[len(payload.Entries)-1].Materialization = "intended"
195 write()
196 pending := read()
197 if got, err := pins.StagedPins(t.Context(), dir); err == nil || got != nil {
198 t.Fatalf("unfinished predecessor %s certified: %v %v", kind, got, err)
199 }
200 if c := certificate(); c.Complete || c.Revision <= initial.Revision {
201 t.Fatal("unfinished registry payload retained certificate")
202 }
203 if !reflect.DeepEqual(pending, read()) {
204 t.Fatal("refusal changed the inventory")
205 }
206 payload.Entries[len(payload.Entries)-1].Materialization = "materialized"
207 write()
208 accept()
209 payload.Entries = payload.Entries[:len(payload.Entries)-1]
210 write()
211 })
212 }
213}
214