carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "github.com/carlosframework/platform/internal/rootstate"
8 "os"
9 "strings"
10 "testing"
11)
12
13func generationEvidence() GenerationEvidence {
14 return GenerationEvidence{Box: "box", Enrollment: "enrollment", Boot: "boot", Migration: 1, Installations: strings.Repeat("a", 64), Launches: strings.Repeat("b", 64), Kernel: strings.Repeat("c", 64)}
15}
16
17func TestPlan14GenerationCompleteness(t *testing.T) {
18 for _, attack := range []string{"none", "missing-verifier", "kernel-unknown", "empty-evidence", "wrong-box", "wrong-enrollment", "changed-reservations", "corrupt-marker", "sync"} {
19 t.Run(attack, func(t *testing.T) {
20 g, s, b := generationFixture(t)
21 if _, err := g.Reserve(b); err != nil {
22 t.Fatal(err)
23 }
24 records, err := g.Reservations(t.Context())
25 if err != nil {
26 t.Fatal(err)
27 }
28 verify := func(context.Context, []GenerationReservation) (GenerationEvidence, error) {
29 e := generationEvidence()
30 switch attack {
31 case "kernel-unknown":
32 return e, os.ErrPermission
33 case "empty-evidence":
34 e.Kernel = ""
35 case "wrong-box":
36 e.Box = "other"
37 case "wrong-enrollment":
38 e.Enrollment = "other"
39 case "changed-reservations":
40 b.Attempt = "another"
41 if _, err := g.Reserve(b); err != nil {
42 t.Fatal(err)
43 }
44 case "sync":
45 s.fail = 2
46 }
47 return e, nil
48 }
49 if attack == "missing-verifier" {
50 verify = nil
51 }
52 if attack == "corrupt-marker" {
53 if err := s.Write(generationCompletenessKey, []byte(`{}`)); err != nil {
54 t.Fatal(err)
55 }
56 }
57 _, err = g.Certify(t.Context(), records, verify)
58 if attack != "none" {
59 if err == nil {
60 t.Fatal("incomplete sources certified")
61 }
62 return
63 }
64 if err != nil {
65 t.Fatal(err)
66 }
67 c, err := g.Completeness()
68 if err != nil || !c.Complete || c.Revision != 1 {
69 t.Fatalf("certificate: %+v %v", c, err)
70 }
71 again, err := g.Certify(t.Context(), records, verify)
72 if err != nil || !again.Complete || again.Revision <= c.Revision || again.Reservations != c.Reservations {
73 t.Fatalf("recheck did not recertify: %+v %v", again, err)
74 }
75 })
76 }
77}
78
79func TestPlan14GenerationCompletenessInvalidation(t *testing.T) {
80 for _, change := range []string{"reserve", "started", "closed", "retired"} {
81 t.Run(change, func(t *testing.T) {
82 g, _, b := generationFixture(t)
83 r, err := g.Reserve(b)
84 if err != nil {
85 t.Fatal(err)
86 }
87 verify := func(context.Context, []GenerationReservation) (GenerationEvidence, error) {
88 return generationEvidence(), nil
89 }
90 records, err := g.Reservations(t.Context())
91 if err != nil {
92 t.Fatal(err)
93 }
94 first, err := g.Certify(t.Context(), records, verify)
95 if err != nil {
96 t.Fatal(err)
97 }
98 switch change {
99 case "reserve":
100 b.Attempt = "new"
101 _, err = g.Reserve(b)
102 case "started":
103 err = g.Started(r, GenerationProcess{PID: 100, TenantPID: 101, Start: "1", TenantStart: "2", Group: "system.slice/unit", Invocation: "invoke", GroupIdentity: b.Executable})
104 default:
105 err = g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { return nil }, func(context.Context, GenerationReservation) (string, error) {
106 if change == "closed" {
107 return "", os.ErrPermission
108 }
109 return strings.Repeat("d", 64), nil
110 })
111 }
112 if err != nil && change != "closed" {
113 t.Fatal(err)
114 }
115 c, err := g.Completeness()
116 if err != nil || c.Complete || c.Revision <= first.Revision {
117 t.Fatalf("mutation left certificate usable: %+v %v", c, err)
118 }
119 records, err = g.Reservations(t.Context())
120 if err != nil {
121 t.Fatal(err)
122 }
123 next, err := g.Certify(t.Context(), records, verify)
124 if err != nil || !next.Complete || next.Reservations == first.Reservations || next.Revision <= c.Revision {
125 t.Fatalf("new revision: %+v %v", next, err)
126 }
127 })
128 }
129}
130
131func TestPlan14GenerationCompletenessDurability(t *testing.T) {
132 g, s, b := generationFixture(t)
133 r, err := g.Reserve(b)
134 if err != nil {
135 t.Fatal(err)
136 }
137 records, err := g.Reservations(t.Context())
138 if err != nil {
139 t.Fatal(err)
140 }
141 verify := func(context.Context, []GenerationReservation) (GenerationEvidence, error) {
142 return generationEvidence(), nil
143 }
144 c, err := g.Certify(t.Context(), records, verify)
145 if err != nil {
146 t.Fatal(err)
147 }
148 s.fail = 1
149 if _, err = g.Completeness(); err == nil {
150 t.Fatal("visible certificate bypassed sync")
151 }
152 g.Store = &generationCompletenessFault{Store: s}
153 b.Attempt = "new"
154 if _, err = g.Reserve(b); err == nil {
155 t.Fatal("unsynced invalidation allowed reserve")
156 }
157 if _, err = g.Read(b); !errors.Is(err, os.ErrNotExist) {
158 t.Fatal("reservation written before invalidation", err)
159 }
160 raw, err := s.Read(generationCompletenessKey)
161 if err != nil {
162 t.Fatal(err)
163 }
164 var pending GenerationCompleteness
165 if err = json.Unmarshal(raw, &pending); err != nil || pending.Complete {
166 t.Fatal("invalidation not visible", err)
167 }
168 reopened := &RootGenerations{Store: s}
169 if _, err = reopened.Reserve(b); err != nil {
170 t.Fatal(err)
171 }
172 if got, err := reopened.Read(r.Binding); err != nil || got != r {
173 t.Fatal("old obligation changed", err)
174 }
175 records, err = reopened.Reservations(t.Context())
176 if err != nil {
177 t.Fatal(err)
178 }
179 next, err := reopened.Certify(t.Context(), records, verify)
180 if err != nil || next.Revision <= c.Revision {
181 t.Fatal("recovery did not advance revision", err)
182 }
183}
184
185type generationCompletenessFault struct{ rootstate.Store }
186
187func (s *generationCompletenessFault) Write(k string, b []byte) error {
188 if err := s.Store.Write(k, b); err != nil {
189 return err
190 }
191 var c GenerationCompleteness
192 if k == generationCompletenessKey && json.Unmarshal(b, &c) == nil && !c.Complete {
193 return os.ErrPermission
194 }
195 return nil
196}
197
198func TestPlan14GenerationCompletenessPinConsumer(t *testing.T) {
199 f := newReuseFixture(t)
200 g, r := generationPinFixture(t, f.opt, "bab", "myapp", "v2")
201 fail := false
202 calls := 0
203 source := RootGenerationPins{Ledger: g, Reconcile: func(context.Context, []GenerationReservation) (GenerationEvidence, error) {
204 calls++
205 if fail {
206 return GenerationEvidence{}, os.ErrPermission
207 }
208 return generationEvidence(), nil
209 }}
210 pins, err := source.StagedPins(t.Context(), f.opt)
211 if err != nil {
212 t.Fatal(err)
213 }
214 if pinned, err := pins.Refers(r.Binding.Path); err != nil || !pinned {
215 t.Fatal("complete snapshot dropped pending pin", err)
216 }
217 c, err := g.Completeness()
218 if err != nil || !c.Complete || calls != 1 {
219 t.Fatalf("pin reader omitted certificate: %+v %v calls=%d", c, err, calls)
220 }
221 fail = true
222 if _, err := source.StagedPins(t.Context(), f.opt); err == nil {
223 t.Fatal("cached completeness survived unknown kernel")
224 }
225 c, err = g.Completeness()
226 if err != nil || c.Complete || calls != 2 {
227 t.Fatalf("failed scan retained certificate: %+v %v calls=%d", c, err, calls)
228 }
229 fail = false
230 if _, err := source.StagedPins(t.Context(), f.opt); err != nil {
231 t.Fatal(err)
232 }
233}
234
235func TestPlan14GenerationCompletenessCertificateSyncFailure(t *testing.T) {
236 g, s, b := generationFixture(t)
237 if _, err := g.Reserve(b); err != nil {
238 t.Fatal(err)
239 }
240 records, err := g.Reservations(t.Context())
241 if err != nil {
242 t.Fatal(err)
243 }
244 g.Store = generationPositiveCertificateFault{Store: s}
245 verify := func(context.Context, []GenerationReservation) (GenerationEvidence, error) {
246 return generationEvidence(), nil
247 }
248 if _, err := g.Certify(t.Context(), records, verify); err == nil {
249 t.Fatal("unsynced certificate returned success")
250 }
251 raw, err := s.Read(generationCompletenessKey)
252 if err != nil {
253 t.Fatal(err)
254 }
255 var visible GenerationCompleteness
256 if err := json.Unmarshal(raw, &visible); err != nil || !visible.Complete {
257 t.Fatal("positive rename was not visible", err)
258 }
259 if _, err := g.Completeness(); err == nil {
260 t.Fatal("unsynced visible record bypassed durability retry")
261 }
262 g.Store = s
263 checked := false
264 if _, err := g.Certify(t.Context(), records, func(context.Context, []GenerationReservation) (GenerationEvidence, error) {
265 checked = true
266 return GenerationEvidence{}, os.ErrPermission
267 }); err == nil || !checked {
268 t.Fatal("reopen reused old evidence")
269 }
270 if _, err := g.Certify(t.Context(), records, verify); err != nil {
271 t.Fatal(err)
272 }
273}
274
275type generationPositiveCertificateFault struct{ rootstate.Store }
276
277func (s generationPositiveCertificateFault) Write(k string, b []byte) error {
278 if err := s.Store.Write(k, b); err != nil {
279 return err
280 }
281 var c GenerationCompleteness
282 if k == generationCompletenessKey && json.Unmarshal(b, &c) == nil && c.Complete {
283 return os.ErrPermission
284 }
285 return nil
286}
287
288func TestPlan14GenerationCompletenessUnknownRevisionAdvances(t *testing.T) {
289 g, _, b := generationFixture(t)
290 r, err := g.Reserve(b)
291 if err != nil {
292 t.Fatal(err)
293 }
294 records, err := g.Reservations(t.Context())
295 if err != nil {
296 t.Fatal(err)
297 }
298 if _, err := g.Certify(t.Context(), records, func(context.Context, []GenerationReservation) (GenerationEvidence, error) {
299 return generationEvidence(), nil
300 }); err != nil {
301 t.Fatal(err)
302 }
303 if err := g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { return nil }, func(context.Context, GenerationReservation) (string, error) { return "", os.ErrPermission }); err == nil {
304 t.Fatal("unknown kernel retired")
305 }
306 before, err := g.Completeness()
307 if err != nil || before.Complete {
308 t.Fatal("missing incomplete state", err)
309 }
310 if err := g.Retire(t.Context(), r, func(context.Context, GenerationReservation) error { return nil }, func(context.Context, GenerationReservation) (string, error) { return strings.Repeat("d", 64), nil }); err != nil {
311 t.Fatal(err)
312 }
313 after, err := g.Completeness()
314 if err != nil || after.Complete || after.Revision <= before.Revision {
315 t.Fatalf("retirement failed to advance unknown revision: before=%+v after=%+v err=%v", before, after, err)
316 }
317}
318