carlos / platform Public

settings-2 task 2: reader-side refusal tests for promotion records

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev fac0f5330e96e006f345d739f6f25508dac973f5 parent 024fb55
1 file changed, +215 −0
  • internal/release/promotion_test.go +215 −0
diff --git a/internal/release/promotion_test.go b/internal/release/promotion_test.go
index 99444b8de..525b74276 100644
--- a/internal/release/promotion_test.go
+++ b/internal/release/promotion_test.go
@@ -2,8 +2,12 @@ package release
import (
"context"
+ "crypto/ed25519"
+ "encoding/base64"
+ "encoding/json"
"errors"
"os"
+ "strings"
"testing"
"time"
)
@@ -142,3 +146,214 @@ func TestWriteKeepsAnIdenticalRecordAndReplacesAnother(t *testing.T) {
t.Fatal("a record for different bytes did not replace the old one")
}
}
+
+// ---- the reader's own refusals ------------------------------------------------
+//
+// WritePromotionRecord refuses every input below before it signs, so these
+// tests hand-build the wrapper and Put it directly: the only thing standing
+// between each record and a pin is ReadPromotionRecord's own check.
+
+// recordPayload is rec as the base64 payload string a wrapper signs.
+func recordPayload(t *testing.T, rec PromotionRecord) string {
+ t.Helper()
+ payload, err := json.MarshalIndent(rec, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+ return base64.StdEncoding.EncodeToString(payload)
+}
+
+// putRecordWrapper writes sw at rec's own record key, replacing whatever is
+// there.
+func putRecordWrapper(t *testing.T, s Store, rec PromotionRecord, sw signedWrapper) {
+ t.Helper()
+ ctx := context.Background()
+ key := PromotionRecordKey(rec.Account, rec.App, rec.Version)
+ _, etag, err := s.Get(ctx, key)
+ if errors.Is(err, os.ErrNotExist) {
+ etag = ""
+ } else if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := s.Put(ctx, key, mustJSON(t, sw), etag); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// putSignedRecord signs rec with key, attaches grant (nil for a root-style
+// wrapper) and writes it, with none of WritePromotionRecord's refusals.
+func putSignedRecord(t *testing.T, s Store, rec PromotionRecord, key ed25519.PrivateKey, grant *signedGrant) signedWrapper {
+ t.Helper()
+ b64 := recordPayload(t, rec)
+ sw := signedWrapper{
+ Payload: b64,
+ Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(key, []byte(b64))),
+ Grant: grant,
+ }
+ putRecordWrapper(t, s, rec, sw)
+ return sw
+}
+
+// handBuiltRecord ships bab/km/v1 and returns a record for it that every
+// reader check accepts, dated an hour into goodGrant's window.
+func handBuiltRecord(t *testing.T) (Store, PromotionRecord) {
+ t.Helper()
+ s := NewDirStore(t.TempDir())
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
+ raw, _, err := s.Get(context.Background(), ManifestKey("bab", "km", "v1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ return s, PromotionRecord{
+ Schema: 1, Account: "bab", App: "km", Version: "v1",
+ ManifestDigest: ManifestDigest(raw), Channel: "canary/x", Actor: "test",
+ At: goodGrant(nil).MintedAt.Add(time.Hour),
+ }
+}
+
+// assertRecordRefused wants ReadPromotionRecord to refuse with an error that
+// names want and is never "absent", and AuthorizedManifest to refuse too.
+func assertRecordRefused(t *testing.T, s Store, pub ed25519.PublicKey, rec PromotionRecord, want string) {
+ t.Helper()
+ ctx := context.Background()
+ got, err := ReadPromotionRecord(ctx, s, pub, rec.Account, rec.App, rec.Version)
+ if err == nil || errors.Is(err, ErrNoPromotionRecord) || !strings.Contains(err.Error(), want) {
+ t.Fatalf("ReadPromotionRecord = %+v, %v; want a refusal naming %q that is not 'absent'", got, err, want)
+ }
+ if m, err := AuthorizedManifest(ctx, s, pub, rec.Account, rec.App, rec.Version); err == nil || errors.Is(err, ErrNoPromotionRecord) {
+ t.Fatalf("AuthorizedManifest = %+v, %v; want a refusal that is not 'absent'", m, err)
+ }
+}
+
+// assertRecordReads is the control: the hand-built wrapper is a real record,
+// so each refusal beside it is the reader's check and not a fixture artefact.
+func assertRecordReads(t *testing.T, s Store, pub ed25519.PublicKey, rec PromotionRecord) {
+ t.Helper()
+ ctx := context.Background()
+ got, err := ReadPromotionRecord(ctx, s, pub, rec.Account, rec.App, rec.Version)
+ if err != nil || got.Channel != rec.Channel || got.ManifestDigest != rec.ManifestDigest {
+ t.Fatalf("control: ReadPromotionRecord = %+v, %v", got, err)
+ }
+ if m, err := AuthorizedManifest(ctx, s, pub, rec.Account, rec.App, rec.Version); err != nil || m.Version != rec.Version {
+ t.Fatalf("control: AuthorizedManifest = %+v, %v", m, err)
+ }
+}
+
+// The reader's signature check: a payload edited after signing, and a record
+// signed by a key that is neither the root nor the key its grant names.
+func TestReaderRefusesABadRecordSignature(t *testing.T) {
+ root, rootPub := testKeys(t)
+ other, otherPub := testKeys(t)
+ t.Run("control", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ putSignedRecord(t, s, rec, root, nil)
+ assertRecordReads(t, s, rootPub, rec)
+ })
+ t.Run("tampered payload", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ sw := putSignedRecord(t, s, rec, root, nil)
+ edited := rec
+ edited.Channel = "stable"
+ sw.Payload = recordPayload(t, edited)
+ putRecordWrapper(t, s, rec, sw)
+ assertRecordRefused(t, s, rootPub, rec, "signature verification failed")
+ })
+ t.Run("signed by a key that is not the root", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ putSignedRecord(t, s, rec, other, nil)
+ assertRecordRefused(t, s, rootPub, rec, "signature verification failed")
+ })
+ t.Run("signed by a key its grant does not name", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ putSignedRecord(t, s, rec, root, forgeGrantSub(t, root, goodGrant(otherPub)))
+ assertRecordRefused(t, s, rootPub, rec, "signature verification failed")
+ })
+}
+
+// The reader's verifyGrant call: a grant signed by anything but the pinned
+// root makes the record it carries worthless.
+func TestReaderRefusesAGrantNotSignedByTheRoot(t *testing.T) {
+ root, rootPub := testKeys(t)
+ impostor, _ := testKeys(t)
+ scoped, scopedPub := testKeys(t)
+ t.Run("control", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ putSignedRecord(t, s, rec, scoped, forgeGrantSub(t, root, goodGrant(scopedPub)))
+ assertRecordReads(t, s, rootPub, rec)
+ })
+ t.Run("grant signed by another key", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ putSignedRecord(t, s, rec, scoped, forgeGrantSub(t, impostor, goodGrant(scopedPub)))
+ assertRecordRefused(t, s, rootPub, rec, "key grant signature verification failed")
+ })
+}
+
+// The reader's "authorization signed by a scoped key" refusal: a grant
+// covers channels, never an authorization, whatever channel rides beside it.
+//
+// Mutation-mindedness: delete that refusal in ReadPromotionRecord and the
+// first case reads as a valid record (canary/x is in scope and in window),
+// while the second fails with scope.allows' "not a rung" instead.
+func TestReaderRefusesAScopedAuthorization(t *testing.T) {
+ root, rootPub := testKeys(t)
+ scoped, scopedPub := testKeys(t)
+ grant := forgeGrantSub(t, root, goodGrant(scopedPub))
+ t.Run("control", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ putSignedRecord(t, s, rec, scoped, grant)
+ assertRecordReads(t, s, rootPub, rec)
+ })
+ for _, tc := range []struct {
+ name string
+ channel string
+ authorized bool
+ want string
+ }{
+ {"authorized with an in-scope channel", "canary/x", true, "an authorization signed by a scoped key"},
+ {"authorized with no channel", "", true, "an authorization signed by a scoped key"},
+ {"no channel and not authorized", "", false, "names no channel"},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ rec.Channel, rec.Authorized = tc.channel, tc.authorized
+ putSignedRecord(t, s, rec, scoped, grant)
+ assertRecordRefused(t, s, rootPub, rec, tc.want)
+ })
+ }
+}
+
+// The reader's scope.allows check: a scoped record must name a channel on a
+// rung its grant covers, dated inside the grant's window.
+//
+// Mutation-mindedness: delete that check in ReadPromotionRecord and every
+// case below reads as a valid record.
+func TestReaderHoldsAScopedRecordToItsGrant(t *testing.T) {
+ root, rootPub := testKeys(t)
+ scoped, scopedPub := testKeys(t)
+ g := goodGrant(scopedPub) // canary and edge
+ grant := forgeGrantSub(t, root, g)
+ t.Run("control", func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ rec.Channel, rec.At = "edge", g.ExpiresAt
+ putSignedRecord(t, s, rec, scoped, grant)
+ assertRecordReads(t, s, rootPub, rec)
+ })
+ for _, tc := range []struct {
+ name string
+ channel string
+ at time.Time
+ want string
+ }{
+ {"stable", "stable", g.MintedAt.Add(time.Hour), "never delegable"},
+ {"a rung the grant does not cover", "beta", g.MintedAt.Add(time.Hour), "is on rung beta"},
+ {"dated after the grant expired", "canary/x", g.ExpiresAt.Add(time.Second), "expired"},
+ {"dated before the grant was minted", "canary/x", g.MintedAt.Add(-time.Second), "predates"},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ s, rec := handBuiltRecord(t)
+ rec.Channel, rec.At = tc.channel, tc.at
+ putSignedRecord(t, s, rec, scoped, grant)
+ assertRecordRefused(t, s, rootPub, rec, tc.want)
+ })
+ }
+}