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 "fmt"
9 "github.com/carlosframework/platform/internal/hostclaim"
10 "github.com/carlosframework/platform/internal/hostwrite"
11 "github.com/carlosframework/platform/internal/park"
12 "github.com/carlosframework/platform/internal/registry"
13 "os"
14 "path/filepath"
15 "testing"
16 "time"
17
18 "github.com/carlosframework/platform/internal/hostbox"
19 "github.com/carlosframework/platform/internal/hostguard"
20 "github.com/carlosframework/platform/internal/provision"
21 "github.com/carlosframework/platform/internal/rootstate"
22)
23
24func TestPlan14RootInventoryEstablishment(t *testing.T) {
25 t.Parallel()
26 for _, attack := range []string{"none", "record etag", "claim etag", "account", "revision", "foreign root"} {
27 t.Run(attack, func(t *testing.T) {
28 f := newRecoveryFixture(t)
29 o := f.read()
30 h := o.Claim.Holder
31 rec := provision.Record{Schema: 1, Host: o.Host, Account: h.Account, App: h.App, Channel: "stable", CreatedAt: f.now}
32 plan14PutRecord(t, f.claims, rec)
33 _, etag, err := f.claims.Get(t.Context(), h.Key)
34 if err != nil {
35 t.Fatalf("%v: %v", err, errors.Unwrap(err))
36 }
37 reg := restartTestRegistry(t)
38 journals, err := hostbox.NewJournalService(f.journal)
39 if err != nil {
40 t.Fatalf("%v: %v", err, errors.Unwrap(err))
41 }
42 guard := f.guard
43 guard.Root = hostguard.NewRootAuthority(&rollbackMemory{files: map[string][]byte{}})
44 roots := map[string]string{"data": t.TempDir(), "opt": t.TempDir(), "units": t.TempDir()}
45 actor := hostbox.Actor{Box: "box-1", Enrollment: "en", Revision: 1, Binding: "1", Region: "ie"}
46 census := &hostbox.EmptyInstallationCensus{Inventory: journals.Inventory, Registry: reg, Launches: f.journal, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "1", Roots: roots}
47 root := ClaimRootHTTP{Journals: journals, Launches: f.journal, Roots: roots, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Reconcile: hostbox.ReconcileAdapter{Registry: reg, Inventory: journals.Inventory, Census: census, Box: actor.Box, Enrollment: actor.Enrollment}, Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: guard, Evidence: f.claims}}}
48 expected := hostbox.InventoryExpectation{RecordKey: h.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Box: actor.Box, Enrollment: actor.Enrollment, ClaimETag: o.ETag, Op: o.Claim.Op.ID, Phase: o.Claim.Op.Phase}}
49 in := hostbox.StopInventory{Complete: true, Host: o.Host, Incarnation: h.Incarnation, InstallationStamp: h.Stamp, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "1"}
50 switch attack {
51 case "record etag":
52 expected.RecordETag = "other"
53 case "claim etag":
54 expected.Target.ClaimETag = "other"
55 case "account":
56 expected.RecordKey = "apps/bap/jam/instances/" + o.Host + ".json"
57 case "revision":
58 expected.Revision = 8
59 case "foreign root":
60 in.Entries = []hostbox.StopInventoryEntry{{Kind: "path", ManagedRoot: "opt", RelativePath: "secret", Ownership: "forged", ArtifactID: "forged", Materialization: "intended"}}
61 }
62 var request hostbox.JournalRequest
63 _, client := hostbox.RemoteJournals(func(ctx context.Context, q hostbox.JournalRequest) (hostbox.JournalResponse, error) {
64 request = q
65 return root.commitRootInventory(ctx, q)
66 })
67 _, err = client.WriteExpected(t.Context(), in, expected)
68 if attack != "none" {
69 if err == nil {
70 t.Fatal("untrusted installation established")
71 }
72 if _, e := guard.Capture(o.Host); e == nil {
73 t.Fatal("refusal wrote root installation")
74 }
75 return
76 }
77 if err != nil {
78 t.Fatalf("%v: %v", err, errors.Unwrap(err))
79 }
80 v, err := guard.Root.View(t.Context(), o.Host, hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp})
81 if err != nil || v.Installation.InventoryRevision != 1 || v.Installation.Record != h.Key {
82 t.Fatalf("installation snapshot: %+v %v", v, err)
83 }
84 if _, err := root.commitRootInventory(t.Context(), request); err != nil {
85 t.Fatal("lost response replay", err)
86 }
87 _, rev, err := journals.Inventory.Snapshot(t.Context(), o.Host, v.Captured)
88 if err != nil || rev != 1 {
89 t.Fatalf("replay changed revision: %d %v", rev, err)
90 }
91 })
92 }
93}
94
95func TestPlan14InventoryRegistrationCompletesWithoutAgentLock(t *testing.T) {
96 t.Parallel()
97 testInventoryRegistration(t, false)
98}
99func TestPlan14RootInventoryPersistentProducer(t *testing.T) { testInventoryRegistration(t, true) }
100func TestPlan14RootInventoryRejectsOldBootMaterialization(t *testing.T) {
101 t.Parallel()
102 testInventoryRegistration(t, true, true)
103}
104
105func testInventoryRegistration(t *testing.T, persistent bool, oldBoot ...bool) {
106 testInventoryRegistrationWith(t, persistent, oldBoot, nil, nil)
107}
108func testInventoryRegistrationWith(t *testing.T, persistent bool, oldBoot []bool, authority rootstate.Store, after func(*testing.T, *recoveryFixture, ClaimRootHTTP, registry.Route, hostbox.Actor)) {
109 cases := []bool{false, true}
110 if after != nil {
111 cases = []bool{false}
112 }
113 for _, stale := range cases {
114 t.Run(fmt.Sprint(stale), func(t *testing.T) {
115 f := newRecoveryFixture(t)
116 if after != nil {
117 o := f.read()
118 if _, err := hostclaim.Update(t.Context(), f.claims, o, hostclaim.AdmitWriter(hostclaim.Admission{ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation, Box: "box-1", Region: "ie", Mode: "cold", NewGeneration: "first-generation"})); err != nil {
119 t.Fatal(err)
120 }
121 }
122 obs := f.read()
123 holder := obs.Claim.Holder
124 rec := provision.Record{Schema: 1, Host: obs.Host, Account: holder.Account, App: holder.App, Channel: "stable", CreatedAt: f.now}
125 plan14PutRecord(t, f.claims, rec)
126 reg := restartTestRegistry(t)
127 journals, err := hostbox.NewJournalService(f.journal)
128 if err != nil {
129 t.Fatal(err)
130 }
131 rootGuard := f.guard
132 rootGuard.Root = hostguard.NewRootAuthority(&rollbackDisk{dir: t.TempDir()})
133 if authority != nil {
134 rootGuard.Root = hostguard.NewRootAuthority(authority)
135 }
136 roots := map[string]string{"data": f.guard.DataDir, "opt": t.TempDir(), "units": t.TempDir()}
137 actor := hostbox.Actor{Box: "box-1", Enrollment: "en", Revision: 1, Binding: "1", Region: "ie"}
138 census := &hostbox.EmptyInstallationCensus{Inventory: journals.Inventory, Registry: reg, Launches: f.journal, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "1", Roots: roots, SharedPaths: map[string][]string{"data": {"hostguard"}}}
139 root := ClaimRootHTTP{Journals: journals, Launches: f.journal, Roots: roots, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Reconcile: hostbox.ReconcileAdapter{Registry: reg, Inventory: journals.Inventory, Census: census, Box: actor.Box, Enrollment: actor.Enrollment}, Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: rootGuard, Evidence: f.claims}}}
140 if persistent {
141 root.inventoryIdentity = rootstate.ObjectIdentity
142 }
143 _, remote := hostbox.RemoteJournals(func(ctx context.Context, q hostbox.JournalRequest) (hostbox.JournalResponse, error) {
144 if q.Op == "inventory-write" {
145 if persistent {
146 for i, e := range q.Inventory.Entries {
147 if e.Kind == "path" {
148 q.Inventory.Entries[i].Identity = rootstate.FileIdentity{Boot: e.Identity.Boot, Filesystem: "fake", Handle: "fake"}
149 if len(oldBoot) > 0 && e.Materialization == "materialized" {
150 q.Inventory.Entries[i].Identity.Boot = "previous-boot"
151 }
152 }
153 }
154 }
155 var out hostbox.JournalResponse
156 var err error
157 if persistent {
158 _, sender := hostbox.RemoteJournals(func(ctx context.Context, signed hostbox.JournalRequest) (hostbox.JournalResponse, error) {
159 var e error
160 out, e = root.commitRootInventory(ctx, signed)
161 if e == nil {
162 if _, replayErr := root.commitRootInventory(ctx, signed); replayErr != nil {
163 t.Fatal("root identity replay", replayErr)
164 }
165 }
166 return out, e
167 })
168 _, err = sender.WriteExpected(ctx, q.Inventory, hostbox.InventoryExpectation{Revision: q.Revision, RecordKey: q.RecordKey, RecordETag: q.RecordETag, Target: q.Target})
169 out.ID = q.ID
170 } else {
171 out, err = root.commitRootInventory(ctx, q)
172 }
173 if stale && q.Revision == 1 && err == nil {
174 rec.RecordSeq++
175 plan14PutRecord(t, f.claims, rec)
176 }
177 return out, err
178 }
179 return journals.Execute(ctx, actor.Box+"/"+actor.Enrollment, q)
180 })
181 guard := f.guard
182 guard.FenceView = rootGuard.Root.View
183 source := hostbox.WorkSource{Guard: guard, Box: actor.Box, Region: "ie", Now: time.Now, Policy: func(context.Context, *hostclaim.Claim, hostbox.Selection, hostwrite.Identity) error { return nil }, Local: func(context.Context, *hostclaim.Claim, hostbox.Selection) error { return nil }}
184 adapter := hostbox.ReconcileAdapter{Registry: reg, Inventory: remote, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "1", Census: census, Reconciler: hostbox.Reconciler{Claims: f.claims, Guard: guard, Box: actor.Box, Region: "ie", Now: time.Now, Writes: hostwrite.Config{Enabled: true, Selector: source}, Local: park.Local{Guard: guard, Claims: f.claims, DataDir: guard.DataDir, Box: actor.Box}, CheckRecord: func(context.Context, hostbox.RecordSelection) error { return nil }, CheckLocal: func(context.Context, hostclaim.Observed, hostbox.RecordSelection) error { return nil }}}
185 row := registry.Route{Host: rec.Host, Account: rec.Account, App: rec.App, Kind: "instance", DBPath: provision.NestedDBPath(guard.DataDir, rec.Host), Socket: provision.NestedSocket(rec.Host)}
186 ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
187 defer cancel()
188 writes := 0
189 err = adapter.Install(ctx, provision.ClaimRecord{Host: rec.Host, Key: holder.Key, Account: rec.Account, App: rec.App, Family: "instance", CreatedAt: rec.CreatedAt, Value: rec, Route: row}, func(owner registry.ClaimOwnership) error {
190 writes++
191 row.ClaimOwnership = owner
192 return reg.Upsert(row)
193 })
194 if stale {
195 if err == nil || writes != 0 {
196 t.Fatalf("stale response applied: %v %d", err, writes)
197 }
198 return
199 }
200 if err != nil || writes != 1 {
201 t.Fatalf("registration failed or blocked: %v (%v), effects=%d", err, errors.Unwrap(err), writes)
202 }
203 _, revision, err := journals.Inventory.Snapshot(t.Context(), rec.Host, hostguard.Captured{Incarnation: holder.Incarnation, Stamp: holder.Stamp})
204 if err != nil || revision != 3 {
205 t.Fatalf("registration did not complete all phases: %d %v", revision, err)
206 }
207 if err := os.Mkdir(filepath.Join(guard.DataDir, "instances"), 0700); err != nil {
208 t.Fatal(err)
209 }
210 work, err := source.Select(t.Context(), rec.Host, "spawn")
211 if err != nil {
212 t.Fatal(err)
213 }
214 roles := &ClaimRoleAssembly{Inventory: remote, ManagedRoots: roots, Guard: guard, Claims: f.claims}
215 err = roles.prepareStateRemote(ctx, f.read(), work, ClaimLaunchInputs{DBPath: row.DBPath}, actor)
216 if len(oldBoot) > 0 {
217 if err == nil {
218 t.Fatal("old-boot numeric identity admitted new materialization")
219 }
220 saved, _, snapshotErr := journals.Inventory.Snapshot(t.Context(), rec.Host, hostguard.Captured{Incarnation: holder.Incarnation, Stamp: holder.Stamp})
221 if snapshotErr != nil {
222 t.Fatal(snapshotErr)
223 }
224 for _, e := range saved.Entries {
225 if e.Kind == "path" && e.Materialization == "materialized" {
226 t.Fatal("refused old boot wrote materialized authority")
227 }
228 }
229 return
230 }
231 if err != nil {
232 t.Fatalf("state registration failed or blocked: %v", err)
233 }
234 saved, revision, err := journals.Inventory.Snapshot(t.Context(), rec.Host, hostguard.Captured{Incarnation: holder.Incarnation, Stamp: holder.Stamp})
235 if persistent && err == nil {
236 for _, e := range saved.Entries {
237 if e.Kind == "path" && (e.Identity.Boot == "" || e.Identity.Filesystem == "fake" || e.Identity.Handle == "fake") {
238 t.Fatal("agent minted root identity", e)
239 }
240 }
241 }
242 if err != nil || revision != 5 {
243 t.Fatalf("state phases missing: %d %v", revision, err)
244 }
245 if after != nil {
246 after(t, f, root, row, actor)
247 }
248
249 })
250 }
251}
252