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 "crypto/ed25519"
5 "errors"
6 "os"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/historyreadback"
12 "github.com/carlosframework/platform/internal/hostactors"
13 "github.com/carlosframework/platform/internal/hostfloor"
14 "github.com/carlosframework/platform/internal/release"
15 "github.com/carlosframework/platform/internal/rootstate"
16)
17
18func TestPlan14HistoryReadbackProduction(t *testing.T) {
19 for _, fault := range []string{"valid", "projection unavailable", "history unsynced", "source changed", "key unavailable", "wrong instance", "pending original"} {
20 t.Run(fault, func(t *testing.T) {
21 e, units, checks, source, signedPub := signedRecoveryFixture(t)
22 rt, err := e.root.reg.Get(observedHost)
23 if err != nil {
24 t.Fatal(err)
25 }
26 h := seedHostRootHistory(t, *rt)
27 pub, key, err := ed25519.GenerateKey(nil)
28 if err != nil {
29 t.Fatal(err)
30 }
31 _, evidenceKey, err := ed25519.GenerateKey(nil)
32 if err != nil {
33 t.Fatal(err)
34 }
35 now := time.Now().UTC()
36 enrollment := hostfloor.Enrollment{Box: "box", Enrollment: strings.Repeat("e", 32), InstanceID: "i-box"}
37 directory := hostactors.Directory{Store: e.store, PublicKey: pub, Bucket: "bucket"}
38 roster := hostactors.Roster{V: 1, DeploymentBucket: "bucket", Revision: 1, Boxes: map[string]hostactors.Box{"box": {Enrollment: enrollment.Enrollment, Namespace: "platform", BoxID: "box", Label: "box", Region: "ie", Pool: "default", Cloud: hostactors.Cloud{Provider: "aws", Account: "123", Region: "eu-west-1", InstanceID: "i-box"}, EvidenceSHA256: strings.Repeat("b", 64), State: "active", CensusPublicKey: evidenceKey.Public().(ed25519.PublicKey)}}, Consoles: map[string]hostactors.Console{}, Holds: map[string]hostactors.Reservation{}, Activations: map[string]hostactors.Reservation{}}
39 wire, err := release.SignRootEnvelope(release.RootSigner(key), roster)
40 if err != nil {
41 t.Fatal(err)
42 }
43 if _, err := e.store.Put(t.Context(), hostactors.Key, wire, ""); err != nil {
44 t.Fatal(err)
45 }
46 b := release.HistoryBinding{Account: rt.Account, App: rt.App, Host: rt.Host}
47 scope := historyreadback.Scope{Bucket: directory.Bucket, Box: enrollment.Box, Enrollment: enrollment.Enrollment, Binding: b}
48 q, err := historyreadback.RequestStatus(t.Context(), directory, release.RootSigner(key), scope, now)
49 if err != nil {
50 t.Fatal(err)
51 }
52 projection := &rollbackMemory{files: map[string][]byte{}}
53 // A public projection can neither supply the signed snapshot nor repair it.
54 projection.files[release.HistoryStatusKey(b.Account, b.App)] = []byte(`{"app_complete":true,"identity":"forged"}`)
55 responder := historyReadbackPublisher{directory: directory, enrollment: enrollment, key: func() (ed25519.PrivateKey, error) {
56 if fault == "key unavailable" {
57 return nil, os.ErrPermission
58 }
59 return evidenceKey, nil
60 }, now: func() time.Time { return now }}
61 if fault == "wrong instance" {
62 responder.enrollment.InstanceID = "i-other"
63 }
64 if fault == "history unsynced" {
65 h.Store = &readbackFailStore{Store: h.Store}
66 }
67 if fault == "source changed" {
68 source.openFile = func(string) (*os.File, error) { return nil, os.ErrPermission }
69 }
70 if fault == "pending original" {
71 n := namedUnitRecovery{source: source, pub: signedPub, readLive: os.Readlink}
72 if _, err := n.recover(t.Context(), rt.Host, checks, func(string) string { return "5s" }, e.root.reg); err != nil {
73 t.Fatal(err)
74 }
75 disk, err := checks.attempts()
76 if err != nil {
77 t.Fatal(err)
78 }
79 pending, err := readRollbackAttempt(disk, rt.Host)
80 if err != nil {
81 t.Fatal(err)
82 }
83 pending.Settled = false
84 if err := writeRollbackState(disk, recoveryResultKey(pending), pending); err != nil {
85 t.Fatal(err)
86 }
87 }
88 m := rootHistoryMaintenance{history: h, store: e.store, source: source, checks: checks, pub: func() (ed25519.PublicKey, error) { return pub, nil }, now: func() time.Time { return now }, projection: func() (rootstate.Store, error) {
89 if fault == "projection unavailable" {
90 return nil, os.ErrPermission
91 }
92 return projection, nil
93 }, readback: responder.publish}
94 before := len(units.effects)
95 _ = m.run(t.Context(), b)
96 receipt, err := historyreadback.ReadStatus(t.Context(), directory, q, now)
97 switch fault {
98 case "valid", "projection unavailable":
99 if err != nil {
100 t.Fatal(err)
101 }
102 if receipt.Status.Identity == "forged" || len(receipt.Status.Hosts) != 1 || receipt.Status.Hosts[0].Source == "" || receipt.Status.RecoverySources == nil {
103 t.Fatalf("bad private readback: %+v", receipt)
104 }
105 case "pending original":
106 if err != nil {
107 t.Fatal(err)
108 }
109 if receipt.Status.RecoverySources == nil || len(receipt.Status.RecoverySources.Unresolved) != 1 {
110 t.Fatal("pending original hidden")
111 }
112 default:
113 if err == nil {
114 t.Fatal("unavailable evidence returned authenticated status")
115 }
116 if _, _, err := e.store.Get(t.Context(), historyreadback.ResponseKey(q)); !errors.Is(err, os.ErrNotExist) {
117 t.Fatalf("response on failure: %v", err)
118 }
119 }
120 if len(units.effects) != before {
121 t.Fatal("readback changed service effects")
122 }
123 })
124 }
125}
126
127type readbackFailStore struct{ rootstate.Store }
128
129func (s *readbackFailStore) Write(string, []byte) error { return os.ErrPermission }
130