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 "encoding/json"
6 "github.com/carlosframework/platform/internal/hostbox"
7 "github.com/carlosframework/platform/internal/hostclaim"
8 "github.com/carlosframework/platform/internal/hostguard"
9 "github.com/carlosframework/platform/internal/provision"
10 "os"
11 "path/filepath"
12 "testing"
13)
14
15func plan14RecoveryRebind(t *testing.T) (*recoveryFixture, ClaimRootHTTP, *rollbackDisk, recoveryRebindRequest) {
16 t.Helper()
17 f, o, h := plan12Handover(t, hostclaim.KindRestoring, "stop")
18 holder := o.Claim.Holder
19 plan14PutRecord(t, f.claims, provision.Record{Schema: 1, Host: o.Host, Account: holder.Account, App: holder.App, Channel: "stable", CreatedAt: f.now})
20 disk := &rollbackDisk{dir: t.TempDir()}
21 g := f.guard
22 g.Root = hostguard.NewRootAuthority(disk)
23 in := hostguard.RootInstallation{Host: o.Host, Incarnation: holder.Incarnation, Stamp: holder.Stamp, Migration: 1, InventoryRevision: 1, Account: holder.Account, App: holder.App, Record: holder.Key, Box: "box-1", Enrollment: "enrollment-1"}
24 if err := g.EstablishRoot(t.Context(), in, func(*hostclaim.Claim) error { return nil }); err != nil {
25 t.Fatal(err)
26 }
27 next, err := hostclaim.Update(t.Context(), f.claims, o, hostclaim.AdmitWriter(hostclaim.Admission{ObservedOpID: o.Claim.Op.ID, ObservedPhase: o.Claim.Op.Phase, ObservedSession: holder.Session, ObservedGeneration: holder.Generation, Box: "box-1", Mode: "cold", NewGeneration: "recovered", Region: "ie", PredecessorRegion: "ie"}))
28 if err != nil {
29 t.Fatal(err)
30 }
31 n := next.Claim.Holder
32 _, etag, err := f.claims.Get(t.Context(), holder.Key)
33 if err != nil {
34 t.Fatal(err)
35 }
36 actor := hostbox.Actor{Box: "box-1", Enrollment: "enrollment-1", Revision: 7, Binding: "7"}
37 root := ClaimRootHTTP{Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: g, Evidence: f.claims}}, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Roots: map[string]string{"data": g.DataDir}, Reconcile: hostbox.ReconcileAdapter{Inventory: h.Inventory, Registry: h.Registry, Box: actor.Box, Enrollment: actor.Enrollment}}
38 q := recoveryRebindRequest{V: 1, Prior: hostguard.Captured{Incarnation: holder.Incarnation, Stamp: holder.Stamp}, Revision: 1, RecordKey: holder.Key, RecordETag: etag, Next: hostbox.ColdSuccessor{Host: next.Host, Incarnation: n.Incarnation, Stamp: n.Stamp, Session: n.Session, Generation: n.Generation, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: "7", ETag: next.ETag, Op: next.Claim.Op.ID, Phase: next.Claim.Op.Phase}}
39 return f, root, disk, q
40}
41func TestPlan14RecoveryRebindAuthority(t *testing.T) {
42 t.Parallel()
43 for _, attack := range []string{"none", "claim etag", "stamp", "session", "generation", "op", "phase", "box", "enrollment", "record etag", "record binding", "revision", "source identity", "missing root", "missing inventory", "plain stopped report", "before commit"} {
44 t.Run(attack, func(t *testing.T) {
45 f, root, disk, q := plan14RecoveryRebind(t)
46 switch attack {
47 case "claim etag":
48 q.Next.ETag = "stale"
49 case "stamp":
50 q.Next.Stamp++
51 case "session":
52 q.Next.Session++
53 case "generation":
54 q.Next.Generation = "other"
55 case "op":
56 q.Next.Op = "other"
57 case "phase":
58 q.Next.Phase = "other"
59 case "box":
60 q.Next.Box = "other"
61 case "enrollment":
62 q.Next.Enrollment = "other"
63 case "record etag":
64 q.RecordETag = "stale"
65 case "record binding":
66 body, oldETag, err := f.claims.Get(t.Context(), q.RecordKey)
67 if err != nil {
68 t.Fatal(err)
69 }
70 var rec provision.Record
71 json.Unmarshal(body, &rec)
72 rec.Account = "other"
73 body, _ = json.Marshal(rec)
74 q.RecordETag, err = f.claims.Put(t.Context(), q.RecordKey, body, oldETag)
75 if err != nil {
76 t.Fatal(err)
77 }
78 case "revision":
79 q.Revision++
80 case "source identity":
81 q.Prior.Stamp++
82 case "missing root":
83 if err := os.Remove(filepath.Join(disk.dir, q.Next.Host+".json")); err != nil {
84 t.Fatal(err)
85 }
86 case "missing inventory":
87 root.Reconcile.Inventory, _ = openLegacyInventory(t, filepath.Join(t.TempDir(), "missing.db"))
88 t.Cleanup(func() { root.Reconcile.Inventory.Close() })
89 case "plain stopped report":
90 o := f.read()
91 o.Claim.Holder.Admissions = nil
92 body, _ := json.Marshal(o.Claim)
93 var err error
94 key, _ := hostclaim.Key(o.Host)
95 q.Next.ETag, err = f.claims.Put(t.Context(), key, body, o.ETag)
96 if err != nil {
97 t.Fatal(err)
98 }
99 case "before commit":
100 disk.afterWrite = func(string, []byte) {
101 disk.afterWrite = nil
102 o := f.read()
103 _, err := hostclaim.Update(t.Context(), f.claims, o, func(c *hostclaim.Claim) error { c.Op.Phase = "changed"; return nil })
104 if err != nil {
105 t.Fatal(err)
106 }
107 }
108 }
109 before, _ := disk.Read(q.Next.Host + ".json")
110 row, _ := root.Reconcile.Registry.Get(q.Next.Host)
111 err := root.rebindRecoveryInventory(t.Context(), q)
112 if attack != "none" {
113 if err == nil {
114 t.Fatal("untrusted rebind accepted")
115 }
116 after, _ := disk.Read(q.Next.Host + ".json")
117 if attack != "before commit" && string(before) != string(after) {
118 t.Fatal("refusal changed root")
119 }
120 _, rev, e := root.Reconcile.Inventory.Snapshot(t.Context(), q.Next.Host, hostguard.Captured{Incarnation: q.Next.Incarnation, Stamp: q.Next.Stamp})
121 if e != nil || rev != 0 {
122 t.Fatal("refusal wrote successor", rev, e)
123 }
124 return
125 }
126 if err != nil {
127 t.Fatal(err)
128 }
129 after, _ := root.Reconcile.Registry.Get(q.Next.Host)
130 if after.ClaimOwnership != row.ClaimOwnership {
131 t.Fatal("root rebound registry")
132 }
133 saved, _ := disk.Read(q.Next.Host + ".json")
134 if err = root.rebindRecoveryInventory(t.Context(), q); err != nil {
135 t.Fatal("replay", err)
136 }
137 again, _ := disk.Read(q.Next.Host + ".json")
138 if string(saved) != string(again) {
139 t.Fatal("replay changed root")
140 }
141 _, rev, err := root.Reconcile.Inventory.Snapshot(t.Context(), q.Next.Host, hostguard.Captured{Incarnation: q.Next.Incarnation, Stamp: q.Next.Stamp})
142 if err != nil || rev != 1 {
143 t.Fatal(rev, err)
144 }
145 })
146 }
147}
148