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 "crypto/ed25519"
6 "encoding/base64"
7 "encoding/json"
8 "errors"
9 "os"
10 "strings"
11 "testing"
12 "time"
13)
14
15func recordFor(t *testing.T, s Store, sg Signer, acct, app, ver, channel string) PromotionRecord {
16 t.Helper()
17 raw, _, err := s.Get(context.Background(), ManifestKey(acct, app, ver))
18 if err != nil {
19 t.Fatal(err)
20 }
21 rec := PromotionRecord{Account: acct, App: app, Version: ver, ManifestDigest: ManifestDigest(raw), Channel: channel, Actor: "test"}
22 if err := sg.WritePromotionRecord(context.Background(), s, rec); err != nil {
23 t.Fatalf("WritePromotionRecord: %v", err)
24 }
25 return rec
26}
27
28func TestPromotionRecordRoundTrip(t *testing.T) {
29 s := NewDirStore(t.TempDir())
30 ctx := context.Background()
31 priv, pub := testKeys(t)
32 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
33 want := recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
34 got, err := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1")
35 if err != nil || got.ManifestDigest != want.ManifestDigest || got.Channel != "stable" || got.At.IsZero() {
36 t.Fatalf("ReadPromotionRecord = %+v, %v", got, err)
37 }
38 if m, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1"); err != nil || m.Version != "v1" {
39 t.Fatalf("AuthorizedManifest = %+v, %v", m, err)
40 }
41}
42
43// Spec §8: a fresh, never-promoted version with a matching manifest is
44// refused for a pin.
45func TestANeverPromotedVersionHasNoRecord(t *testing.T) {
46 s := NewDirStore(t.TempDir())
47 ctx := context.Background()
48 _, pub := testKeys(t)
49 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
50 _, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1")
51 if !errors.Is(err, ErrNoPromotionRecord) || !errors.Is(err, os.ErrNotExist) {
52 t.Fatalf("AuthorizedManifest = %v; want ErrNoPromotionRecord wrapping os.ErrNotExist", err)
53 }
54}
55
56// Spec §8 "Promotion binding": a reused version name with a replayed record
57// and a different manifest is refused.
58func TestAReplayedRecordDoesNotAuthorizeAReusedName(t *testing.T) {
59 s := NewDirStore(t.TempDir())
60 ctx := context.Background()
61 priv, pub := testKeys(t)
62 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("promoted bytes"))
63 recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
64 saved, _, _ := s.Get(ctx, PromotionRecordKey("bab", "km", "v1"))
65 if err := DeleteVersion(ctx, s, "bab", "km", "v1"); err != nil {
66 t.Fatal(err)
67 }
68 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("nobody promoted these"))
69 _, etag, gerr := s.Get(ctx, PromotionRecordKey("bab", "km", "v1"))
70 if errors.Is(gerr, os.ErrNotExist) {
71 etag = ""
72 }
73 if _, err := s.Put(ctx, PromotionRecordKey("bab", "km", "v1"), saved, etag); err != nil {
74 t.Fatal(err)
75 }
76 if _, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1"); !errors.Is(err, ErrManifestDigest) {
77 t.Fatalf("AuthorizedManifest = %v; want ErrManifestDigest", err)
78 }
79}
80
81// Review Focus 2.
82func TestARecordCopiedToAnotherKeyIsRefused(t *testing.T) {
83 s := NewDirStore(t.TempDir())
84 ctx := context.Background()
85 priv, pub := testKeys(t)
86 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
87 recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
88 raw, _, _ := s.Get(ctx, PromotionRecordKey("bab", "km", "v1"))
89 if _, err := s.Put(ctx, PromotionRecordKey("bab", "blog", "v1"), raw, ""); err != nil {
90 t.Fatal(err)
91 }
92 if _, err := ReadPromotionRecord(ctx, s, pub, "bab", "blog", "v1"); err == nil || errors.Is(err, ErrNoPromotionRecord) {
93 t.Fatalf("a copied record = %v; want a refusal that is not 'absent'", err)
94 }
95}
96
97func TestScopedRecordsFollowTheGrant(t *testing.T) {
98 s := NewDirStore(t.TempDir())
99 ctx := context.Background()
100 root, pub := testKeys(t)
101 seed, grant, err := MintKeyGrant(root, "ci", []string{"canary"}, time.Hour)
102 if err != nil {
103 t.Fatal(err)
104 }
105 key, _ := ParseSigningKey(seed, "test")
106 sg, err := ScopedSigner(key, grant, pub)
107 if err != nil {
108 t.Fatal(err)
109 }
110 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
111 raw, _, _ := s.Get(ctx, ManifestKey("bab", "km", "v1"))
112 d := ManifestDigest(raw)
113 if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Channel: "canary/x"}); err != nil {
114 t.Fatalf("in scope: %v", err)
115 }
116 if rec, err := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1"); err != nil || rec.Channel != "canary/x" {
117 t.Fatalf("scoped record = %+v, %v", rec, err)
118 }
119 if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Channel: "stable"}); err == nil {
120 t.Fatal("a canary-scoped key signed a stable record")
121 }
122 if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Authorized: true}); err == nil {
123 t.Fatal("a scoped key signed an authorize record")
124 }
125}
126
127func TestWriteKeepsAnIdenticalRecordAndReplacesAnother(t *testing.T) {
128 s := NewDirStore(t.TempDir())
129 ctx := context.Background()
130 priv, pub := testKeys(t)
131 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
132 first := recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "canary/x")
133 got1, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1")
134 time.Sleep(1100 * time.Millisecond)
135 recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
136 got2, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1")
137 if !got2.At.Equal(got1.At) || got2.Channel != "canary/x" {
138 t.Fatalf("an identical digest replaced the record: %+v -> %+v", got1, got2)
139 }
140 other := first
141 other.ManifestDigest = ManifestDigest([]byte("another manifest"))
142 if err := RootSigner(priv).WritePromotionRecord(ctx, s, other); err != nil {
143 t.Fatal(err)
144 }
145 if got3, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1"); got3.ManifestDigest != other.ManifestDigest {
146 t.Fatal("a record for different bytes did not replace the old one")
147 }
148}
149
150// ---- the reader's own refusals ------------------------------------------------
151//
152// WritePromotionRecord refuses every input below before it signs, so these
153// tests hand-build the wrapper and Put it directly: the only thing standing
154// between each record and a pin is ReadPromotionRecord's own check.
155
156// recordPayload is rec as the base64 payload string a wrapper signs.
157func recordPayload(t *testing.T, rec PromotionRecord) string {
158 t.Helper()
159 payload, err := json.MarshalIndent(rec, "", " ")
160 if err != nil {
161 t.Fatal(err)
162 }
163 return base64.StdEncoding.EncodeToString(payload)
164}
165
166// putRecordWrapper writes sw at rec's own record key, replacing whatever is
167// there.
168func putRecordWrapper(t *testing.T, s Store, rec PromotionRecord, sw signedWrapper) {
169 t.Helper()
170 ctx := context.Background()
171 key := PromotionRecordKey(rec.Account, rec.App, rec.Version)
172 _, etag, err := s.Get(ctx, key)
173 if errors.Is(err, os.ErrNotExist) {
174 etag = ""
175 } else if err != nil {
176 t.Fatal(err)
177 }
178 if _, err := s.Put(ctx, key, mustJSON(t, sw), etag); err != nil {
179 t.Fatal(err)
180 }
181}
182
183// putSignedRecord signs rec with key, attaches grant (nil for a root-style
184// wrapper) and writes it, with none of WritePromotionRecord's refusals.
185func putSignedRecord(t *testing.T, s Store, rec PromotionRecord, key ed25519.PrivateKey, grant *signedGrant) signedWrapper {
186 t.Helper()
187 b64 := recordPayload(t, rec)
188 sw := signedWrapper{
189 Payload: b64,
190 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(key, []byte(b64))),
191 Grant: grant,
192 }
193 putRecordWrapper(t, s, rec, sw)
194 return sw
195}
196
197// handBuiltRecord ships bab/km/v1 and returns a record for it that every
198// reader check accepts, dated an hour into goodGrant's window.
199func handBuiltRecord(t *testing.T) (Store, PromotionRecord) {
200 t.Helper()
201 s := NewDirStore(t.TempDir())
202 shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
203 raw, _, err := s.Get(context.Background(), ManifestKey("bab", "km", "v1"))
204 if err != nil {
205 t.Fatal(err)
206 }
207 return s, PromotionRecord{
208 Schema: 1, Account: "bab", App: "km", Version: "v1",
209 ManifestDigest: ManifestDigest(raw), Channel: "canary/x", Actor: "test",
210 At: goodGrant(nil).MintedAt.Add(time.Hour),
211 }
212}
213
214// assertRecordRefused wants ReadPromotionRecord to refuse with an error that
215// names want and is never "absent", and AuthorizedManifest to refuse too.
216func assertRecordRefused(t *testing.T, s Store, pub ed25519.PublicKey, rec PromotionRecord, want string) {
217 t.Helper()
218 ctx := context.Background()
219 got, err := ReadPromotionRecord(ctx, s, pub, rec.Account, rec.App, rec.Version)
220 if err == nil || errors.Is(err, ErrNoPromotionRecord) || !strings.Contains(err.Error(), want) {
221 t.Fatalf("ReadPromotionRecord = %+v, %v; want a refusal naming %q that is not 'absent'", got, err, want)
222 }
223 if m, err := AuthorizedManifest(ctx, s, pub, rec.Account, rec.App, rec.Version); err == nil || errors.Is(err, ErrNoPromotionRecord) {
224 t.Fatalf("AuthorizedManifest = %+v, %v; want a refusal that is not 'absent'", m, err)
225 }
226}
227
228// assertRecordReads is the control: the hand-built wrapper is a real record,
229// so each refusal beside it is the reader's check and not a fixture artefact.
230func assertRecordReads(t *testing.T, s Store, pub ed25519.PublicKey, rec PromotionRecord) {
231 t.Helper()
232 ctx := context.Background()
233 got, err := ReadPromotionRecord(ctx, s, pub, rec.Account, rec.App, rec.Version)
234 if err != nil || got.Channel != rec.Channel || got.ManifestDigest != rec.ManifestDigest {
235 t.Fatalf("control: ReadPromotionRecord = %+v, %v", got, err)
236 }
237 if m, err := AuthorizedManifest(ctx, s, pub, rec.Account, rec.App, rec.Version); err != nil || m.Version != rec.Version {
238 t.Fatalf("control: AuthorizedManifest = %+v, %v", m, err)
239 }
240}
241
242// The reader's signature check: a payload edited after signing, and a record
243// signed by a key that is neither the root nor the key its grant names.
244func TestReaderRefusesABadRecordSignature(t *testing.T) {
245 root, rootPub := testKeys(t)
246 other, otherPub := testKeys(t)
247 t.Run("control", func(t *testing.T) {
248 s, rec := handBuiltRecord(t)
249 putSignedRecord(t, s, rec, root, nil)
250 assertRecordReads(t, s, rootPub, rec)
251 })
252 t.Run("tampered payload", func(t *testing.T) {
253 s, rec := handBuiltRecord(t)
254 sw := putSignedRecord(t, s, rec, root, nil)
255 edited := rec
256 edited.Channel = "stable"
257 sw.Payload = recordPayload(t, edited)
258 putRecordWrapper(t, s, rec, sw)
259 assertRecordRefused(t, s, rootPub, rec, "signature verification failed")
260 })
261 t.Run("signed by a key that is not the root", func(t *testing.T) {
262 s, rec := handBuiltRecord(t)
263 putSignedRecord(t, s, rec, other, nil)
264 assertRecordRefused(t, s, rootPub, rec, "signature verification failed")
265 })
266 t.Run("signed by a key its grant does not name", func(t *testing.T) {
267 s, rec := handBuiltRecord(t)
268 putSignedRecord(t, s, rec, root, forgeGrantSub(t, root, goodGrant(otherPub)))
269 assertRecordRefused(t, s, rootPub, rec, "signature verification failed")
270 })
271}
272
273// The reader's verifyGrant call: a grant signed by anything but the pinned
274// root makes the record it carries worthless.
275func TestReaderRefusesAGrantNotSignedByTheRoot(t *testing.T) {
276 root, rootPub := testKeys(t)
277 impostor, _ := testKeys(t)
278 scoped, scopedPub := testKeys(t)
279 t.Run("control", func(t *testing.T) {
280 s, rec := handBuiltRecord(t)
281 putSignedRecord(t, s, rec, scoped, forgeGrantSub(t, root, goodGrant(scopedPub)))
282 assertRecordReads(t, s, rootPub, rec)
283 })
284 t.Run("grant signed by another key", func(t *testing.T) {
285 s, rec := handBuiltRecord(t)
286 putSignedRecord(t, s, rec, scoped, forgeGrantSub(t, impostor, goodGrant(scopedPub)))
287 assertRecordRefused(t, s, rootPub, rec, "key grant signature verification failed")
288 })
289}
290
291// The reader's "authorization signed by a scoped key" refusal: a grant
292// covers channels, never an authorization, whatever channel rides beside it.
293//
294// Mutation-mindedness: delete that refusal in ReadPromotionRecord and the
295// first case reads as a valid record (canary/x is in scope and in window),
296// while the second fails with scope.allows' "not a rung" instead.
297func TestReaderRefusesAScopedAuthorization(t *testing.T) {
298 root, rootPub := testKeys(t)
299 scoped, scopedPub := testKeys(t)
300 grant := forgeGrantSub(t, root, goodGrant(scopedPub))
301 t.Run("control", func(t *testing.T) {
302 s, rec := handBuiltRecord(t)
303 putSignedRecord(t, s, rec, scoped, grant)
304 assertRecordReads(t, s, rootPub, rec)
305 })
306 for _, tc := range []struct {
307 name string
308 channel string
309 authorized bool
310 want string
311 }{
312 {"authorized with an in-scope channel", "canary/x", true, "an authorization signed by a scoped key"},
313 {"authorized with no channel", "", true, "an authorization signed by a scoped key"},
314 {"no channel and not authorized", "", false, "names no channel"},
315 } {
316 t.Run(tc.name, func(t *testing.T) {
317 s, rec := handBuiltRecord(t)
318 rec.Channel, rec.Authorized = tc.channel, tc.authorized
319 putSignedRecord(t, s, rec, scoped, grant)
320 assertRecordRefused(t, s, rootPub, rec, tc.want)
321 })
322 }
323}
324
325// The reader's scope.allows check: a scoped record must name a channel on a
326// rung its grant covers, dated inside the grant's window.
327//
328// Mutation-mindedness: delete that check in ReadPromotionRecord and every
329// case below reads as a valid record.
330func TestReaderHoldsAScopedRecordToItsGrant(t *testing.T) {
331 root, rootPub := testKeys(t)
332 scoped, scopedPub := testKeys(t)
333 g := goodGrant(scopedPub) // canary and edge
334 grant := forgeGrantSub(t, root, g)
335 t.Run("control", func(t *testing.T) {
336 s, rec := handBuiltRecord(t)
337 rec.Channel, rec.At = "edge", g.ExpiresAt
338 putSignedRecord(t, s, rec, scoped, grant)
339 assertRecordReads(t, s, rootPub, rec)
340 })
341 for _, tc := range []struct {
342 name string
343 channel string
344 at time.Time
345 want string
346 }{
347 {"stable", "stable", g.MintedAt.Add(time.Hour), "never delegable"},
348 {"a rung the grant does not cover", "beta", g.MintedAt.Add(time.Hour), "is on rung beta"},
349 {"dated after the grant expired", "canary/x", g.ExpiresAt.Add(time.Second), "expired"},
350 {"dated before the grant was minted", "canary/x", g.MintedAt.Add(-time.Second), "predates"},
351 } {
352 t.Run(tc.name, func(t *testing.T) {
353 s, rec := handBuiltRecord(t)
354 rec.Channel, rec.At = tc.channel, tc.at
355 putSignedRecord(t, s, rec, scoped, grant)
356 assertRecordRefused(t, s, rootPub, rec, tc.want)
357 })
358 }
359}
360