| index 6776ec750..663eceea6 100644 |
| --- a/internal/release/heldauth_test.go |
| +++ b/internal/release/heldauth_test.go |
| @@ -6,6 +6,7 @@ import ( |
| "errors" |
| "os" |
| "path/filepath" |
| + "strings" |
| "testing" |
| |
| "github.com/carlosframework/platform/internal/registry" |
| @@ -311,9 +312,11 @@ func TestHeldAuthorizedPrefersASignedDigest(t *testing.T) { |
| } |
| } |
| |
| -// A pointer that does not name the build never reaches the ratchet: it is |
| -// not evidence, so it neither marks its channel nor, when the mark cannot be |
| -// written, holds up a build another pointer authorizes. |
| +// Every verified pointer a held stage reads goes through the ratchet (spec |
| +// §4.6, "once verified"; ruling R15): one that does not name the build is not |
| +// evidence, but its digests still mark its channel. Its ratchet outcome never |
| +// changes the answer, so a mark that cannot be written does not hold up a |
| +// build another pointer authorizes. |
| func TestHeldAuthorizedAdmitsOnlyNamingPointers(t *testing.T) { |
| s := NewDirStore(t.TempDir()) |
| ctx := context.Background() |
| @@ -330,7 +333,129 @@ func TestHeldAuthorizedAdmitsOnlyNamingPointers(t *testing.T) { |
| if want, err := HeldAuthorized(ctx, s, pub, NewDigestRatchet(dir, nil, false), "bab", "km", "v1"); err != nil || want != "" { |
| t.Fatalf("HeldAuthorized = %q, %v; want channel b's pointer trust", want, err) |
| } |
| - if _, err := os.Stat(markPath(dir, "bab", "km", "a")); err == nil { |
| - t.Fatal("a pointer that does not name the build was admitted") |
| + if _, err := os.Stat(markPath(dir, "bab", "km", "a")); err != nil { |
| + t.Fatalf("channel a's verified digests left no mark: %v", err) |
| } |
| + if os.Geteuid() != 0 { |
| + ro := filepath.Join(t.TempDir(), "ro") |
| + if err := os.MkdirAll(ro, 0o555); err != nil { |
| + t.Fatal(err) |
| + } |
| + if want, err := HeldAuthorized(ctx, s, pub, NewDigestRatchet(ro, nil, false), "bab", "km", "v1"); err != nil || want != "" { |
| + t.Fatalf("HeldAuthorized with channel a's mark unwritable = %q, %v; want channel b's pointer trust", want, err) |
| + } |
| + } |
| +} |
| + |
| +// heldNothingDownloaded fails the test if the held stage read any of |
| +// version's release objects or left anything in the version store. |
| +func heldNothingDownloaded(t *testing.T, rec *heldRecordingStore, opt, version string) { |
| + t.Helper() |
| + rel := ReleasesPrefix("bab", "km") + version + "/" |
| + rec.mu.Lock() |
| + defer rec.mu.Unlock() |
| + for _, c := range rec.calls { |
| + if strings.HasPrefix(c, "get "+rel) { |
| + t.Fatalf("the held stage downloaded %s", strings.TrimPrefix(c, "get ")) |
| + } |
| + } |
| + if _, err := os.Stat(VersionPath(opt, "bab", version, "km")); err == nil { |
| + t.Fatalf("%s was staged", version) |
| + } |
| +} |
| + |
| +// heldSubstitute replaces version's shipped bytes under the same name. |
| +func heldSubstitute(t *testing.T, s Store, version string) { |
| + t.Helper() |
| + if err := DeleteVersion(context.Background(), s, "bab", "km", version); err != nil { |
| + t.Fatal(err) |
| + } |
| + shipRelease(t, s, "bab", "km", version, adoptPlatform, []byte("nobody promoted these")) |
| +} |
| + |
| +// Astra final A1, ruling R16: a pointer whose head signs a digest, naming a |
| +// legacy history entry that signs none, is no evidence for that entry ("never |
| +// a rollback target once its channel has any digest", spec §4.6). With no |
| +// record, its replaced bytes are refused with the flag off and on, and |
| +// nothing is downloaded. |
| +func TestHeldStageRefusesADigestlessEntryOnADigestedChannel(t *testing.T) { |
| + for name, require := range map[string]bool{"flag off": false, "flag on": true} { |
| + t.Run(name, func(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + pointerOnly(t, s, priv, []string{"canary/x"}, nil) // v1, digestless |
| + if err := WriteDigestMarker(ctx, s, RootSigner(priv), "bab", "test"); err != nil { |
| + t.Fatal(err) |
| + } |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("v2")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v2"); err != nil { |
| + t.Fatal(err) |
| + } |
| + p, err := Channel(ctx, s, pub, "bab", "km", "canary/x") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if p.Version != "v2" || p.Digest == "" || len(p.History) != 1 || p.History[0] != "v1" || p.DigestOf("v1") != "" { |
| + t.Fatalf("pointer = %+v; want a digested head v2 over a digestless history v1", p) |
| + } |
| + heldSubstitute(t, s, "v1") |
| + rec := &heldRecordingStore{Store: s} |
| + if _, err := StageHeld(ctx, rec, pub, NewDigestRatchet(t.TempDir(), nil, require), opt, heldAuthRoute("a.example.com"), "v1"); !errors.Is(err, ErrHeldNotAuthorized) { |
| + t.Fatalf("StageHeld of a digestless entry on a digested channel = %v; want ErrHeldNotAuthorized", err) |
| + } |
| + heldNothingDownloaded(t, rec, opt, "v1") |
| + }) |
| + } |
| +} |
| + |
| +// Astra final A2, ruling R15: a held stage remembers every verified pointer |
| +// with digests it reads, naming the build or not. Pass 1 stages v1 on channel |
| +// b's word while channel a's pointer signs digests for v2; pass 2, on the |
| +// same ratchet, is a saved digestless pointer of channel a replayed over |
| +// substituted v2 bytes, and is refused. |
| +func TestHeldStageRemembersEveryVerifiedPointer(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("v1")) |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("v2")) |
| + _, v2raw, err := GetManifestRaw(ctx, s, "bab", "km", "v2") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + sign := func(p *ChannelPointer) []byte { |
| + t.Helper() |
| + w, err := signPointer(RootSigner(priv), p) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + return w |
| + } |
| + saved := sign(&ChannelPointer{Account: "bab", App: "km", Channel: "a", Version: "v2"}) // signed before digests |
| + put := func(channel string, w []byte) { |
| + t.Helper() |
| + _, etag, err := s.Get(ctx, ChannelKey("bab", "km", channel)) |
| + if err != nil && !errors.Is(err, os.ErrNotExist) { |
| + t.Fatal(err) |
| + } |
| + if _, err := s.Put(ctx, ChannelKey("bab", "km", channel), w, etag); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + put("a", sign(&ChannelPointer{Account: "bab", App: "km", Channel: "a", Version: "v2", Digest: ManifestDigest(v2raw)})) |
| + put("b", sign(&ChannelPointer{Account: "bab", App: "km", Channel: "b", Version: "v1"})) |
| + ratchet := NewDigestRatchet(t.TempDir(), nil, false) |
| + if _, err := StageHeld(ctx, s, pub, ratchet, opt, heldAuthRoute("a.example.com"), "v1"); err != nil { |
| + t.Fatalf("pass 1, v1 on channel b's word: %v", err) |
| + } |
| + put("a", saved) |
| + heldSubstitute(t, s, "v2") |
| + rec := &heldRecordingStore{Store: s} |
| + if _, err := StageHeld(ctx, rec, pub, ratchet, opt, heldAuthRoute("a.example.com"), "v2"); !errors.Is(err, ErrHeldNotAuthorized) { |
| + t.Fatalf("pass 2, a replayed digestless pointer over substituted v2 = %v; want ErrHeldNotAuthorized", err) |
| + } |
| + heldNothingDownloaded(t, rec, opt, "v2") |
| } |