carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/hostbox"
11 "github.com/carlosframework/platform/internal/hostclaim"
12 "github.com/carlosframework/platform/internal/hostguard"
13 "github.com/carlosframework/platform/internal/provision"
14 "github.com/carlosframework/platform/internal/release"
15 "github.com/carlosframework/platform/internal/rootparticipation"
16 "github.com/carlosframework/platform/internal/rootstate"
17)
18
19func TestPlan14GenerationInventoryCompleteness(t *testing.T) {
20 for _, attack := range []string{"none", "empty-reservations", "retired-only", "missing-installation", "missing-launch", "extra-launch", "extra-installation", "wrong-controller", "wrong-inputs", "wrong-installation", "wrong-process", "wrong-census", "unknown-migration", "incomplete-census", "kernel", "changed-snapshot"} {
21 t.Run(attack, func(t *testing.T) {
22 f, disk, _ := generationLaunchFixture(t)
23 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
24 t.Fatal(err)
25 }
26 r := readGenerationLaunch(t, disk)
27 view, err := f.root.Guard.Root.View(t.Context(), f.target.Host, hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp})
28 if err != nil {
29 t.Fatal(err)
30 }
31 launch, err := f.journal.Read(t.Context(), f.target)
32 if err != nil {
33 t.Fatal(err)
34 }
35 snapshot := generationInventory{Census: rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}, Payloads: generationPayloadFixture([]hostguard.FenceView{view}), Installations: []hostguard.FenceView{view}, Launches: []hostbox.LaunchInventory{launch}}
36 records := []release.GenerationReservation{r}
37 switch attack {
38 case "empty-reservations":
39 records = nil
40 case "retired-only":
41 records[0].Closed = true
42 records[0].Retirement = r.Binding.Digest
43 case "missing-installation":
44 snapshot.Installations = nil
45 case "missing-launch":
46 snapshot.Launches = nil
47 case "extra-launch":
48 extra := launch
49 extra.Target.Host = "other.example"
50 snapshot.Launches = append(snapshot.Launches, extra)
51 case "extra-installation":
52 extra := view
53 extra.Host = "other.example"
54 extra.Installation.Host = extra.Host
55 extra.Installation.Record = provision.RecordKey(extra.Installation.Account, extra.Installation.App, extra.Host)
56 snapshot.Installations = append(snapshot.Installations, extra)
57 case "wrong-controller":
58 snapshot.Launches[0].ControllerPID++
59 case "wrong-inputs":
60 snapshot.Launches[0].Reservations[0].Inputs = "other"
61 case "wrong-installation":
62 records[0].Binding.Installation = "other"
63 case "wrong-process":
64 records[0].Process.PID++
65 case "wrong-census":
66 snapshot.Census.Enrollment = "other"
67 case "unknown-migration":
68 snapshot.Census.Migration = 0
69 case "incomplete-census":
70 snapshot.Census.Complete = false
71 }
72 reads, scans := 0, 0
73 verifier := generationReconciler{read: func(context.Context) (generationInventory, error) {
74 reads++
75 out := snapshot
76 if attack == "changed-snapshot" && reads > 1 {
77 out.Census.Revision++
78 }
79 return out, nil
80 }, kernel: func(context.Context) (string, string, error) {
81 scans++
82 if attack == "kernel" {
83 return "", "", os.ErrPermission
84 }
85 return "boot-now", r.Binding.Digest, nil
86 }}
87 evidence, err := verifier.reconcile(t.Context(), records)
88 if attack == "none" {
89 if err != nil || scans != 1 || reads != 2 || evidence.Box != f.target.Box || evidence.Installations == "" || evidence.Launches == "" {
90 t.Fatalf("positive: %+v %v reads=%d scans=%d", evidence, err, reads, scans)
91 }
92 } else if err == nil {
93 t.Fatalf("%s certified: %+v", attack, evidence)
94 }
95 })
96 }
97}
98
99func TestPlan14GenerationInventoryEmptyNeedsIndependentSources(t *testing.T) {
100 snapshot := generationInventory{Census: rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}}
101 for _, missing := range []string{"none", "inventory", "kernel", "census"} {
102 t.Run(missing, func(t *testing.T) {
103 v := generationReconciler{read: func(context.Context) (generationInventory, error) {
104 if missing == "census" {
105 return generationInventory{}, nil
106 }
107 return snapshot, nil
108 }, kernel: func(context.Context) (string, string, error) { return "boot", strings.Repeat("a", 64), nil }}
109 if missing == "inventory" {
110 v.read = nil
111 }
112 if missing == "kernel" {
113 v.kernel = nil
114 }
115 _, err := v.reconcile(t.Context(), nil)
116 if (err == nil) != (missing == "none") {
117 t.Fatalf("empty: %v", err)
118 }
119 })
120 }
121}
122
123func TestPlan14GenerationInventoryPinConsumer(t *testing.T) {
124 for _, attack := range []string{"none", "omitted-reservation", "kernel", "lost-census", "missing-payload", "payload-revision"} {
125 t.Run(attack, func(t *testing.T) {
126 f, disk, _ := generationLaunchFixture(t)
127 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
128 t.Fatal(err)
129 }
130 r := readGenerationLaunch(t, disk)
131 view, err := f.root.Guard.Root.View(t.Context(), f.target.Host, hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp})
132 if err != nil {
133 t.Fatal(err)
134 }
135 launch, err := f.journal.Read(t.Context(), f.target)
136 if err != nil {
137 t.Fatal(err)
138 }
139 reads := 0
140 v := generationReconciler{read: func(context.Context) (generationInventory, error) {
141 reads++
142 c := rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}
143 if attack == "lost-census" {
144 c.Complete = false
145 }
146 payloads := generationPayloadFixture([]hostguard.FenceView{view})
147 if attack == "missing-payload" {
148 payloads = nil
149 }
150 if attack == "payload-revision" {
151 payloads[0].Revision++
152 }
153 return generationInventory{Census: c, Payloads: payloads, Installations: []hostguard.FenceView{view}, Launches: []hostbox.LaunchInventory{launch}}, nil
154 }, kernel: func(context.Context) (string, string, error) {
155 if attack == "kernel" {
156 return "", "", os.ErrPermission
157 }
158 return "boot", r.Binding.Digest, nil
159 }}
160 if attack == "omitted-reservation" {
161 names, err := disk.Names()
162 if err != nil {
163 t.Fatal(err)
164 }
165 for _, name := range names {
166 if err := disk.Remove(name); err != nil {
167 t.Fatal(err)
168 }
169 }
170 }
171 s := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, reconcile: v.reconcile}
172 pins, err := s.StagedPins(t.Context(), f.root.generations.optDir)
173 if attack != "none" {
174 if err == nil {
175 t.Fatalf("unsafe snapshot: %+v", pins)
176 }
177 return
178 }
179 if err != nil || reads != 2 {
180 t.Fatalf("pins: %v reads=%d", err, reads)
181 }
182 if pinned, err := pins.Refers(r.Binding.Path); err != nil || !pinned {
183 t.Fatalf("missing exact pin: %v", err)
184 }
185 c, err := (&release.RootGenerations{Store: disk}).Completeness()
186 if err != nil || !c.Complete {
187 t.Fatalf("certificate: %+v %v", c, err)
188 }
189 })
190 }
191 s, ok := productionRootGenerationPins().(generationPinSource)
192 if !ok || s.reconcile == nil {
193 t.Fatal("production reconciliation not connected")
194 }
195}
196
197func TestPlan14GenerationInventoryLifecycle(t *testing.T) {
198 for _, mode := range []string{"pending", "pending-wrong-controller", "retired", "retained-rebind", "unrecognized-predecessor"} {
199 t.Run(mode, func(t *testing.T) {
200 f, disk, _ := generationLaunchFixture(t)
201 prior, err := f.journal.Read(t.Context(), f.target)
202 if err != nil {
203 t.Fatal(err)
204 }
205 journal, err := hostbox.OpenLaunchJournal(filepath.Join(t.TempDir(), "launch.db"))
206 if err != nil {
207 t.Fatal(err)
208 }
209 defer journal.Close()
210 if err := journal.Prelaunch(t.Context(), prior); err != nil {
211 t.Fatal(err)
212 }
213 f.root.Journal, f.journal = journal, journal
214 pending := strings.HasPrefix(mode, "pending")
215 if pending {
216 if _, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req); err != nil {
217 t.Fatal(err)
218 }
219 } else {
220 if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil {
221 t.Fatal(err)
222 }
223 if err := f.root.stop(t.Context(), f.peer, *f.req.Reservation); err != nil {
224 t.Fatal(err)
225 }
226 if err := journal.CloseWriters(t.Context(), f.target); err != nil {
227 t.Fatal(err)
228 }
229 }
230 r := readGenerationLaunch(t, disk)
231 if mode == "retired" {
232 v := generationRetirement{guard: f.root.Guard, journal: journal, kernel: func(context.Context, release.GenerationReservation) (string, error) { return r.Binding.Digest, nil }}
233 if err := v.retire(t.Context(), &release.RootGenerations{Store: disk}, r); err != nil {
234 t.Fatal(err)
235 }
236 }
237 if mode == "retained-rebind" || mode == "unrecognized-predecessor" {
238 captured := hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp}
239 in, err := f.root.Guard.Root.Installation(t.Context(), f.target.Host, captured)
240 if err != nil {
241 t.Fatal(err)
242 }
243 next := in
244 next.Stamp += 3
245 next.InventoryRevision++
246 f.claims.set(func(c *hostclaim.Claim) { c.Holder.Stamp = next.Stamp })
247 if err := f.root.Guard.RebindRootInventory(t.Context(), captured, in.InventoryRevision, next, []byte("verified-rebind"), func(*hostclaim.Claim) error { return nil }, func() error { return nil }); err != nil {
248 t.Fatal(err)
249 }
250 }
251 v := generationReconciler{read: func(ctx context.Context) (generationInventory, error) {
252 views, err := f.root.Guard.Root.GenerationInstallations(ctx)
253 if err != nil {
254 return generationInventory{}, err
255 }
256 launches, err := journal.GenerationLaunches(ctx)
257 if err != nil {
258 return generationInventory{}, err
259 }
260 if mode == "unrecognized-predecessor" {
261 views[0].Retired = nil
262 }
263 if mode == "pending-wrong-controller" {
264 launches[0].ControllerPID++
265 }
266 return generationInventory{Census: rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}, Payloads: generationPayloadFixture(views), Installations: views, Launches: launches}, nil
267 }, kernel: func(context.Context) (string, string, error) { return "boot", r.Binding.Digest, nil }}
268 source := generationPinSource{open: func() (rootstate.Store, error) { return disk, nil }, reconcile: v.reconcile}
269 pins, err := source.StagedPins(t.Context(), f.root.generations.optDir)
270 if mode == "pending-wrong-controller" || mode == "unrecognized-predecessor" {
271 if err == nil {
272 t.Fatal("unbound generation certified")
273 }
274 return
275 }
276 if err != nil {
277 t.Fatal(err)
278 }
279 pinned, err := pins.Refers(r.Binding.Path)
280 if err != nil || pinned != (mode != "retired") {
281 t.Fatalf("exact lifecycle pin=%v: %v", pinned, err)
282 }
283 })
284 }
285}
286