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 production flag (2026-08-04 console bake controls spec §2–§3): a
4// root-signed per-app object deciding whether the supply-chain bake applies.
5// The properties tested here, in the order they'd hurt if lost: missing is
6// the default (nil, nil); a set flag round-trips; a scoped key's wrapper is
7// refused even with a valid root grant; a tampered payload is refused; the
8// key sits OUTSIDE channels/ so pointer listings never try to parse it.
9
10import (
11 "context"
12 "crypto/ed25519"
13 "crypto/rand"
14 "encoding/base64"
15 "encoding/json"
16 "errors"
17 "os"
18 "strings"
19 "testing"
20 "time"
21)
22
23func TestProductionFlagDefaultIsMissing(t *testing.T) {
24 s := NewDirStore(t.TempDir())
25 _, pub := testKeys(t)
26 f, err := GetProduction(context.Background(), s, pub, "bab", "kass")
27 if err != nil || f != nil {
28 t.Fatalf("GetProduction on empty store = %v, %v; want nil, nil (the default: not production)", f, err)
29 }
30}
31
32func TestProductionFlagRoundTrips(t *testing.T) {
33 s := NewDirStore(t.TempDir())
34 ctx := context.Background()
35 priv, pub := testKeys(t)
36 if err := SetProduction(ctx, s, priv, "bab", "kass", true, "paul@keymail.dev"); err != nil {
37 t.Fatalf("SetProduction: %v", err)
38 }
39 f, err := GetProduction(ctx, s, pub, "bab", "kass")
40 if err != nil {
41 t.Fatalf("GetProduction: %v", err)
42 }
43 if f == nil || !f.Production || f.Actor != "paul@keymail.dev" || f.SetAt.IsZero() {
44 t.Fatalf("flag = %+v, want production on, actor recorded, set_at stamped", f)
45 }
46 // Flip it off over the existing object — the CAS path.
47 if err := SetProduction(ctx, s, priv, "bab", "kass", false, "paul@keymail.dev"); err != nil {
48 t.Fatalf("SetProduction off: %v", err)
49 }
50 if f, _ = GetProduction(ctx, s, pub, "bab", "kass"); f == nil || f.Production {
51 t.Fatalf("flag = %+v after flipping off, want production false", f)
52 }
53}
54
55// The flag key lives beside channels/, never inside it: ChannelsPrefix
56// listings are parsed as pointers by the console and CLI.
57func TestProductionKeyIsOutsideChannels(t *testing.T) {
58 if k := ProductionKey("bab", "kass"); k != "apps/bab/kass/production.json" {
59 t.Fatalf("ProductionKey = %q", k)
60 }
61 if strings.HasPrefix(ProductionKey("bab", "kass"), ChannelsPrefix("bab", "kass")) {
62 t.Fatal("production flag key sits under channels/ — pointer listings would try to parse it")
63 }
64}
65
66// A scoped key must not be able to mint the flag: a wrapper carrying ANY
67// grant is refused before signature checking, and a wrapper signed by a key
68// other than the root fails signature checking. Together: root only.
69func TestProductionFlagRefusesScopedAndForeignKeys(t *testing.T) {
70 s := NewDirStore(t.TempDir())
71 ctx := context.Background()
72 priv, pub := testKeys(t)
73
74 // Build a validly-shaped flag payload signed by a DIFFERENT key.
75 otherPub, otherPriv, err := ed25519.GenerateKey(rand.Reader)
76 if err != nil {
77 t.Fatal(err)
78 }
79 _ = otherPub
80 payload, _ := json.Marshal(ProductionFlag{Schema: 1, Account: "bab", App: "kass",
81 Production: false, SetAt: timeNowTestHelper()})
82 b64 := base64.StdEncoding.EncodeToString(payload)
83 wrapper, _ := json.Marshal(signedWrapper{
84 Payload: b64,
85 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(otherPriv, []byte(b64))),
86 })
87 if _, err := s.Put(ctx, ProductionKey("bab", "kass"), wrapper, ""); err != nil {
88 t.Fatal(err)
89 }
90 if _, err := GetProduction(ctx, s, pub, "bab", "kass"); err == nil {
91 t.Fatal("a flag signed by a non-root key verified")
92 }
93
94 // A wrapper WITH a grant is refused outright — even a grant the root
95 // really signed. Mint a real scoped key and have it sign the flag.
96 seed, grantB64, err := MintKeyGrant(priv, "builder", []string{RungEdge}, maxGrantTTL)
97 if err != nil {
98 t.Fatal(err)
99 }
100 scopedKey, err := ParseSigningKey(seed, "test")
101 if err != nil {
102 t.Fatal(err)
103 }
104 sg, err := parseGrantBlob(grantB64, "test")
105 if err != nil {
106 t.Fatal(err)
107 }
108 wrapper, _ = json.Marshal(signedWrapper{
109 Payload: b64,
110 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(scopedKey, []byte(b64))),
111 Grant: sg,
112 })
113 if _, err := s.Put(ctx, ProductionKey("bab", "kass"), wrapper, etagOfKey(t, s, "bab", "kass")); err != nil {
114 t.Fatal(err)
115 }
116 _, err = GetProduction(ctx, s, pub, "bab", "kass")
117 if err == nil || !strings.Contains(err.Error(), "root") {
118 t.Fatalf("a grant-bearing flag wrapper verified (err=%v), want a root-only refusal", err)
119 }
120}
121
122// etagOfKey reads the current etag of an app's flag so a test can CAS over it.
123func etagOfKey(t *testing.T, s Store, acct, app string) string {
124 t.Helper()
125 _, etag, err := s.Get(context.Background(), ProductionKey(acct, app))
126 if err != nil {
127 t.Fatal(err)
128 }
129 return etag
130}
131
132func TestProductionFlagRefusesWrongApp(t *testing.T) {
133 s := NewDirStore(t.TempDir())
134 ctx := context.Background()
135 priv, pub := testKeys(t)
136 if err := SetProduction(ctx, s, priv, "bab", "kass", true, ""); err != nil {
137 t.Fatal(err)
138 }
139 // Copy kass's flag to keymail's key — a cross-app replay must be refused
140 // because the SIGNED payload names the app.
141 raw, _, err := s.Get(ctx, ProductionKey("bab", "kass"))
142 if err != nil {
143 t.Fatal(err)
144 }
145 if _, err := s.Put(ctx, ProductionKey("bab", "keymail"), raw, ""); err != nil {
146 t.Fatal(err)
147 }
148 if _, err := GetProduction(ctx, s, pub, "bab", "keymail"); err == nil {
149 t.Fatal("another app's flag verified under keymail's key")
150 }
151}
152
153// The signature binds the exact base64 payload STRING, not the decoded JSON
154// meaning — so a payload that is decoded, edited, and re-encoded (even
155// leaving the original Sig in place) must fail verification. A verifier that
156// decoded first and checked a signature over re-marshalled bytes would let
157// this through if the re-marshalling happened to reproduce the tampered
158// bytes; this test pins the "no canonicalisation" wire rule directly against
159// the field an attacker would actually want to flip.
160func TestProductionFlagRefusesTamperedPayload(t *testing.T) {
161 s := NewDirStore(t.TempDir())
162 ctx := context.Background()
163 priv, pub := testKeys(t)
164 if err := SetProduction(ctx, s, priv, "bab", "kass", true, "paul@keymail.dev"); err != nil {
165 t.Fatal(err)
166 }
167 raw, etag, err := s.Get(ctx, ProductionKey("bab", "kass"))
168 if err != nil {
169 t.Fatal(err)
170 }
171 var sw signedWrapper
172 if err := json.Unmarshal(raw, &sw); err != nil {
173 t.Fatal(err)
174 }
175 payload, err := base64.StdEncoding.DecodeString(sw.Payload)
176 if err != nil {
177 t.Fatal(err)
178 }
179 var f ProductionFlag
180 if err := json.Unmarshal(payload, &f); err != nil {
181 t.Fatal(err)
182 }
183 if !f.Production {
184 t.Fatal("expected production=true before tampering")
185 }
186 f.Production = false // flip the bit an attacker would want to flip
187 tampered, err := json.Marshal(&f)
188 if err != nil {
189 t.Fatal(err)
190 }
191 // Keep the ORIGINAL Sig — it covers the ORIGINAL payload string. A
192 // re-encoded payload, even with identical decoded meaning, must fail
193 // verification without a fresh signature.
194 sw.Payload = base64.StdEncoding.EncodeToString(tampered)
195 wrapper, err := json.Marshal(sw)
196 if err != nil {
197 t.Fatal(err)
198 }
199 if _, err := s.Put(ctx, ProductionKey("bab", "kass"), wrapper, etag); err != nil {
200 t.Fatal(err)
201 }
202 if _, err := GetProduction(ctx, s, pub, "bab", "kass"); err == nil {
203 t.Fatal("a tampered payload (re-encoded, original sig) verified")
204 }
205}
206
207// A trashed app cannot be flagged production in EITHER direction: restore
208// first. This holds even though the flag would refuse to WRITE anything
209// destructive itself — the point is that a production flag appearing on a
210// trashed app contradicts the flag's own promise (production = never
211// deletable), so the door closes before the CAS write, not after.
212func TestSetProductionRefusesTrashedApp(t *testing.T) {
213 s := NewDirStore(t.TempDir())
214 ctx := context.Background()
215 priv, _ := testKeys(t)
216
217 if err := TrashApp(ctx, s, "bab", "derp", "paul@keymail.dev", time.Now()); err != nil {
218 t.Fatal(err)
219 }
220 if err := SetProduction(ctx, s, priv, "bab", "derp", true, "paul@keymail.dev"); err == nil {
221 t.Fatal("SetProduction(true) on a trashed app succeeded, want a refusal")
222 }
223 // The OFF direction is refused too — there is nothing legitimate to do to
224 // a trashed app's flag; restore first.
225 if err := SetProduction(ctx, s, priv, "bab", "derp", false, "paul@keymail.dev"); err == nil {
226 t.Fatal("SetProduction(false) on a trashed app succeeded, want a refusal")
227 }
228 // No flag object should have been written.
229 if _, _, err := s.Get(ctx, ProductionKey("bab", "derp")); err == nil {
230 t.Fatal("a refused SetProduction on a trashed app wrote the flag object anyway")
231 }
232}
233
234// An unreadable trash marker refuses too — the usual direction: a caller
235// about to declare production must never act as though a marker it cannot
236// verify simply isn't there. Uses a store that fails ONLY the tombstone
237// key (everything else, including the eventual production.json write,
238// would otherwise succeed) so this test actually pins the new trash-marker
239// check rather than any generic "the store is broken" refusal.
240func TestSetProductionRefusesUnreadableTrashMarker(t *testing.T) {
241 priv, _ := testKeys(t)
242 real := NewDirStore(t.TempDir())
243 s := &tombstoneReadFailStore{Store: real, key: AppTombstoneKey("bab", "derp"), err: errors.New("bucket on fire")}
244 if err := SetProduction(context.Background(), s, priv, "bab", "derp", true, "paul@keymail.dev"); err == nil {
245 t.Fatal("SetProduction with an unreadable trash marker succeeded, want a refusal")
246 }
247 // Confirm nothing else about the store is broken — the refusal really is
248 // the trash-marker check, not a store that happens to fail everything.
249 if _, _, err := real.Get(context.Background(), ProductionKey("bab", "derp")); !errors.Is(err, os.ErrNotExist) {
250 t.Fatalf("production flag written despite an unreadable trash marker: err=%v", err)
251 }
252}
253
254// tombstoneReadFailStore fails Get for exactly one key and delegates
255// everything else to the wrapped Store.
256type tombstoneReadFailStore struct {
257 Store
258 key string
259 err error
260}
261
262func (t *tombstoneReadFailStore) Get(ctx context.Context, key string) ([]byte, string, error) {
263 if key == t.key {
264 return nil, "", t.err
265 }
266 return t.Store.Get(ctx, key)
267}
268
269// timeNowTestHelper is a stamped, truncated now — what signProductionFlag
270// itself writes — for tests that hand-build payloads.
271func timeNowTestHelper() time.Time { return time.Now().UTC().Truncate(time.Second) }
272
273// Task-4-review fix 5 (Minor, promoted), tightened by re-review: SetAt is
274// truncated to the second, so two flips within the same wall-clock second
275// would otherwise share a set_at — and the box-side replay floor (bake.go's
276// NewestSetAt) treats an equal set_at as nothing new, silently swallowing
277// the second flip on every box that already saw the first. A single
278// before/after pair is vacuous if the two calls happen to straddle a
279// second boundary (it would pass with or without the bump). Looping several
280// rapid flips and requiring EVERY consecutive pair to be strictly
281// increasing removes that escape: the bump guarantees strict monotonicity
282// regardless of whether any given pair lands in the same truncated second
283// or crosses one, so this holds deterministically with the fix in place.
284func TestSetProductionMonotonicSetAtOnRapidFlip(t *testing.T) {
285 s := NewDirStore(t.TempDir())
286 ctx := context.Background()
287 priv, pub := testKeys(t)
288
289 var prev time.Time
290 for i := 0; i < 3; i++ {
291 if err := SetProduction(ctx, s, priv, "bab", "kass", i%2 == 0, "paul@keymail.dev"); err != nil {
292 t.Fatalf("SetProduction flip %d: %v", i, err)
293 }
294 f, err := GetProduction(ctx, s, pub, "bab", "kass")
295 if err != nil || f == nil {
296 t.Fatalf("GetProduction after flip %d: %v, %v", i, f, err)
297 }
298 if i > 0 && !f.SetAt.After(prev) {
299 t.Fatalf("flip %d's set_at %v not strictly after flip %d's %v (same-second collision not bumped)",
300 i, f.SetAt, i-1, prev)
301 }
302 prev = f.SetAt
303 }
304}
305