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
3// The host-retry log (readiness round 2, Task 3; Astra plan 9): signed
4// `carlos instances retry` grants ride their own object, which no released
5// console reads or rewrites, so an older console that re-marshals the
6// restart-scope log cannot strip a grant's signature, and an older box
7// never sees one.
8
9import (
10 "context"
11 "encoding/json"
12 "strings"
13 "testing"
14 "time"
15)
16
17func TestHostRetryKeyIsItsOwnObjectUnderTheAppPrefix(t *testing.T) {
18 if got, want := HostRetryKey("bab", "titogo"), "apps/bab/titogo/host-retry.json"; got != want {
19 t.Fatalf("HostRetryKey = %q, want %q", got, want)
20 }
21 if HostRetryKey("bab", "titogo") == RestartScopeKey("bab", "titogo") || HostRetryKey("bab", "titogo") == RestartGenKey("bab", "titogo") {
22 t.Fatal("the host-retry log shares a key with a restart signal")
23 }
24}
25
26func TestAppendHostRetryRoundTrip(t *testing.T) {
27 s := NewDirStore(t.TempDir())
28 ctx := context.Background()
29 pub, priv := hostRetryKey(t)
30 if g, found, err := LoadHostRetries(ctx, s, "bab", "titogo"); err != nil || found || g == nil || g.Schema != 1 || len(g.Requests) != 0 {
31 t.Fatalf("absent log = %+v, %v, %v; want an empty schema-1 log, not found", g, found, err)
32 }
33 wire, err := SignHostRetry(RootSigner(priv), "bab", "titogo", "a.example.com", "v2", "book-1", 9, time.Now())
34 if err != nil {
35 t.Fatal(err)
36 }
37 first, err := AppendHostRetry(ctx, s, "bab", "titogo", "a.example.com", wire)
38 if err != nil {
39 t.Fatalf("append: %v", err)
40 }
41 second, err := AppendHostRetry(ctx, s, "bab", "titogo", "b.example.com", wire)
42 if err != nil {
43 t.Fatalf("second append: %v", err)
44 }
45 if first.ID == "" || first.ID == second.ID {
46 t.Fatalf("ids %q and %q: want two distinct requests", first.ID, second.ID)
47 }
48 if _, err := time.Parse(time.RFC3339, first.RequestedAt); err != nil {
49 t.Fatalf("RequestedAt %q: %v", first.RequestedAt, err)
50 }
51 g, found, err := LoadHostRetries(ctx, s, "bab", "titogo")
52 if err != nil || !found {
53 t.Fatalf("load = %v, found %v", err, found)
54 }
55 if g.Schema != 1 || len(g.Requests) != 2 || g.Requests[0].ID != first.ID || g.Requests[1].ID != second.ID {
56 t.Fatalf("log = %+v, want both requests, oldest first", g)
57 }
58 r := g.Requests[0]
59 if r.Scope.Kind != RestartScopeRetry || r.Scope.Value != "a.example.com" || r.Scope.Signed != wire {
60 t.Fatalf("entry = %+v", r)
61 }
62 if _, err := VerifyHostRetry(pub, r.Scope.Signed, "bab", "titogo", "a.example.com", time.Now()); err != nil {
63 t.Fatalf("the stored grant does not verify: %v", err)
64 }
65 // The same JSON shape as the restart-scope log.
66 raw, _, err := s.Get(ctx, HostRetryKey("bab", "titogo"))
67 if err != nil {
68 t.Fatal(err)
69 }
70 var shape struct {
71 Schema int `json:"schema"`
72 Requests []struct {
73 ID string `json:"id"`
74 RequestedAt string `json:"requested_at"`
75 Scope struct {
76 Kind, Value, Signed string
77 } `json:"scope"`
78 } `json:"requests"`
79 }
80 if err := json.Unmarshal(raw, &shape); err != nil || shape.Schema != 1 || len(shape.Requests) != 2 || shape.Requests[0].Scope.Signed != wire {
81 t.Fatalf("wire = %s (%v)", raw, err)
82 }
83 // Nothing landed in the restart-scope log.
84 if _, found, _ := LoadRestartScopes(ctx, s, "bab", "titogo"); found {
85 t.Fatal("a host retry wrote the restart-scope log")
86 }
87}
88
89func TestAppendHostRetryRefusesWhatNoBoxCouldTake(t *testing.T) {
90 s := NewDirStore(t.TempDir())
91 ctx := context.Background()
92 for _, tc := range []struct{ name, acct, app, host, signed string }{
93 {"unsigned", "bab", "titogo", "a.example.com", ""},
94 {"no host", "bab", "titogo", "", "x"},
95 {"bad host", "bab", "titogo", "a\nb", "x"},
96 {"oversized grant", "bab", "titogo", "a.example.com", strings.Repeat("x", MaxRootEnvelopeBytes+1)},
97 {"bad account", "../etc", "titogo", "a.example.com", "x"},
98 {"bad app", "bab", "../x", "a.example.com", "x"},
99 } {
100 if _, err := AppendHostRetry(ctx, s, tc.acct, tc.app, tc.host, tc.signed); err == nil {
101 t.Errorf("%s: appended", tc.name)
102 }
103 }
104 if _, found, _ := LoadHostRetries(ctx, s, "bab", "titogo"); found {
105 t.Fatal("a refused append wrote the log")
106 }
107}
108
109func TestHostRetryLogIsBoundedAndRetriesACASConflict(t *testing.T) {
110 cs := &conflictOnceStore{Store: NewDirStore(t.TempDir())}
111 ctx := context.Background()
112 if _, err := AppendHostRetry(ctx, cs, "bab", "titogo", "a.example.com", "x"); err != nil || cs.conflicts != 1 {
113 t.Fatalf("append through one conflict = %v (conflicts %d)", err, cs.conflicts)
114 }
115 var last RestartRequest
116 for i := 0; i < restartScopeKeep+5; i++ {
117 r, err := AppendHostRetry(ctx, cs, "bab", "titogo", "a.example.com", "x")
118 if err != nil {
119 t.Fatal(err)
120 }
121 last = r
122 }
123 g, _, err := LoadHostRetries(ctx, cs, "bab", "titogo")
124 if err != nil {
125 t.Fatal(err)
126 }
127 if len(g.Requests) != restartScopeKeep || g.Newest() != last.ID {
128 t.Fatalf("log holds %d (newest %q), want %d ending at %q", len(g.Requests), g.Newest(), restartScopeKeep, last.ID)
129 }
130}
131
132func TestHostRetryLogRefusesANewerSchema(t *testing.T) {
133 s := NewDirStore(t.TempDir())
134 ctx := context.Background()
135 if _, err := s.Put(ctx, HostRetryKey("bab", "titogo"), []byte(`{"schema":2,"requests":[]}`), ""); err != nil {
136 t.Fatal(err)
137 }
138 if _, _, err := LoadHostRetries(ctx, s, "bab", "titogo"); err == nil {
139 t.Fatal("a schema-2 log parsed")
140 }
141 if _, err := AppendHostRetry(ctx, s, "bab", "titogo", "a.example.com", "x"); err == nil {
142 t.Fatal("appended to a schema-2 log")
143 }
144}
145
146// Recon D §2 failure (a): a console built before RestartScope.Signed
147// re-marshals the whole restart-scope log on every append, and so strips
148// every pending grant's signature in it. The grants are not in it any
149// more: such a rewrite leaves the host-retry log byte for byte as it was.
150func TestARestartScopeRewriteWithoutSignedLeavesHostRetriesAlone(t *testing.T) {
151 s := NewDirStore(t.TempDir())
152 ctx := context.Background()
153 _, priv := hostRetryKey(t)
154 wire, err := SignHostRetry(RootSigner(priv), "bab", "titogo", "a.example.com", "v2", "book-1", 9, time.Now())
155 if err != nil {
156 t.Fatal(err)
157 }
158 if _, err := AppendHostRetry(ctx, s, "bab", "titogo", "a.example.com", wire); err != nil {
159 t.Fatal(err)
160 }
161 before, beforeTag, err := s.Get(ctx, HostRetryKey("bab", "titogo"))
162 if err != nil {
163 t.Fatal(err)
164 }
165 if _, err := AppendRestartScope(ctx, s, "bab", "titogo", RestartScope{Kind: RestartScopeHost, Value: "b.example.com"}); err != nil {
166 t.Fatal(err)
167 }
168 // The older console's append: its struct has no Signed.
169 type oldScope struct {
170 Kind string `json:"kind"`
171 Value string `json:"value"`
172 }
173 type oldRequest struct {
174 ID string `json:"id"`
175 RequestedAt string `json:"requested_at"`
176 Scope oldScope `json:"scope"`
177 }
178 var old struct {
179 Schema int `json:"schema"`
180 Requests []oldRequest `json:"requests"`
181 }
182 raw, tag, err := s.Get(ctx, RestartScopeKey("bab", "titogo"))
183 if err != nil {
184 t.Fatal(err)
185 }
186 if err := json.Unmarshal(raw, &old); err != nil {
187 t.Fatal(err)
188 }
189 old.Requests = append(old.Requests, oldRequest{ID: "beef", RequestedAt: time.Now().UTC().Format(time.RFC3339), Scope: oldScope{Kind: RestartScopeHost, Value: "c.example.com"}})
190 body, _ := json.Marshal(old)
191 if _, err := s.Put(ctx, RestartScopeKey("bab", "titogo"), body, tag); err != nil {
192 t.Fatal(err)
193 }
194 after, afterTag, err := s.Get(ctx, HostRetryKey("bab", "titogo"))
195 if err != nil {
196 t.Fatal(err)
197 }
198 if string(after) != string(before) || afterTag != beforeTag {
199 t.Fatalf("the host-retry log moved under a restart-scope rewrite:\n%s\n%s", before, after)
200 }
201 g, _, err := LoadHostRetries(ctx, s, "bab", "titogo")
202 if err != nil || len(g.Requests) != 1 || g.Requests[0].Scope.Signed != wire {
203 t.Fatalf("host retries = %+v, %v; want the signed grant intact", g, err)
204 }
205}
206
207// No new write can put a retry, or any signature, in the restart-scope log:
208// grants ride host-retry.json only (AppendHostRetry). A box still reads a
209// signed retry an older console left there, with the same checks.
210func TestValidRestartScopeRefusesARetryAndASignature(t *testing.T) {
211 for _, sc := range []RestartScope{
212 {Kind: RestartScopeRetry, Value: "a.example.com"},
213 {Kind: RestartScopeRetry, Value: "a.example.com", Signed: `{"payload":"x","sig":"y"}`},
214 {Kind: RestartScopeHost, Value: "a.example.com", Signed: "x"},
215 {Kind: RestartScopeEnvironment, Value: "relay", Signed: "x"},
216 } {
217 if err := ValidRestartScope(sc); err == nil {
218 t.Errorf("ValidRestartScope(%+v) accepted it", sc)
219 }
220 }
221 s := NewDirStore(t.TempDir())
222 if _, err := AppendRestartScope(context.Background(), s, "bab", "titogo", RestartScope{Kind: RestartScopeRetry, Value: "a.example.com", Signed: "x"}); err == nil {
223 t.Fatal("a retry was appended to the restart-scope log")
224 }
225}
226