| new file mode 100644 |
| index 000000000..6a1db4bd6 |
| --- /dev/null |
| +++ b/internal/release/adoptdigest_test.go |
| @@ -0,0 +1,238 @@ |
| +package release |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "os" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +// replayPointer writes back a saved pointer, the attacker's move in #253. |
| +func replayPointer(t *testing.T, s Store, ch string, saved []byte) { |
| + t.Helper() |
| + ctx := context.Background() |
| + _, etag, err := s.Get(ctx, ChannelKey("bab", "km", ch)) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := s.Put(ctx, ChannelKey("bab", "km", ch), saved, etag); err != nil { |
| + t.Fatal(err) |
| + } |
| +} |
| + |
| +// Spec §8: a reused version name under a replayed digest-bearing pointer is |
| +// refused before staging. |
| +func TestAReusedNameUnderAReplayedPointerIsRefusedBeforeStaging(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + markerOn(t, s, priv, "bab") |
| + |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("the bytes that were promoted")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + saved, _, _ := s.Get(ctx, ChannelKey("bab", "km", "canary/x")) |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v2"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := DeleteVersion(ctx, s, "bab", "km", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("bytes nobody promoted")) |
| + replayPointer(t, s, "canary/x", saved) |
| + |
| + addRoute(t, reg, "a.example.com", "km", "canary/x", false) |
| + var out bytes.Buffer |
| + withCapturedStdout(t, &out, func() { |
| + if err := Pass(ctx, reg, s, pub, opt, nil); err != nil { |
| + t.Fatalf("Pass: %v", err) |
| + } |
| + }) |
| + if v, _ := LiveVersion(opt, "a.example.com"); v != "" { |
| + t.Fatalf("live on %q: the replayed pointer installed bytes nobody promoted", v) |
| + } |
| + if _, err := os.Stat(VersionPath(opt, "bab", "v1", "km")); err == nil { |
| + t.Fatal("the reused name's artifact was staged") |
| + } |
| + if !strings.Contains(out.String(), "pointer signed") { |
| + t.Fatalf("no refusal line:\n%s", out.String()) |
| + } |
| +} |
| + |
| +func TestAStagedCopyThatIsNotTheSignedBuildIsRefused(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + markerOn(t, s, priv, "bab") |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("signed")) |
| + if err := StageArtifact(opt, "bab", "v1", "km", []byte("left over from a pruned v1")); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "a.example.com", "km", "canary/x", false) |
| + var out bytes.Buffer |
| + withCapturedStdout(t, &out, func() { _ = Pass(ctx, reg, s, pub, opt, nil) }) |
| + if v, _ := LiveVersion(opt, "a.example.com"); v != "" { |
| + t.Fatalf("live on %q over a staged copy that is not the signed build", v) |
| + } |
| +} |
| + |
| +func TestADigestBearingPointerAdoptsTheSignedBuild(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + markerOn(t, s, priv, "bab") |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "a.example.com", "km", "canary/x", false) |
| + if err := Pass(ctx, reg, s, pub, opt, nil); err != nil { |
| + t.Fatal(err) |
| + } |
| + if v, _ := LiveVersion(opt, "a.example.com"); v != "v1" { |
| + t.Fatalf("live %q, want v1", v) |
| + } |
| + sib, err := os.ReadFile(StagedManifestPath(opt, "bab", "v1", "km")) |
| + if err != nil || ManifestDigest(sib) != manifestDigestOf(t, s, "bab", "km", "v1") { |
| + t.Fatalf("the staged sibling is not the checked canonical bytes: %v", err) |
| + } |
| +} |
| + |
| +// A rollback's head is an old history entry: its digest moved with it. |
| +func TestARolledBackHeadIsCheckedAgainstItsDigest(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + markerOn(t, s, priv, "bab") |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) |
| + for _, v := range []string{"v1", "v2"} { |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", v); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if err := DeleteVersion(ctx, s, "bab", "km", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("other bytes, same name")) |
| + if err := Rollback(ctx, s, priv, "bab", "km", "canary/x"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "a.example.com", "km", "canary/x", false) |
| + withCapturedStdout(t, &bytes.Buffer{}, func() { _ = Pass(ctx, reg, s, pub, opt, nil) }) |
| + if v, _ := LiveVersion(opt, "a.example.com"); v == "v1" { |
| + t.Fatal("a rollback onto a reused name installed its new bytes") |
| + } |
| +} |
| + |
| +func TestTheRatchetRefusesALegacyPointerInThePass(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + legacy, _, _ := s.Get(ctx, ChannelKey("bab", "km", "canary/x")) |
| + markerOn(t, s, priv, "bab") |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v2"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "a.example.com", "km", "canary/x", false) |
| + r := NewDigestRatchet(t.TempDir(), nil, false) |
| + if err := Pass(ctx, reg, s, pub, opt, nil, WithDigestRatchet(r)); err != nil { |
| + t.Fatal(err) |
| + } |
| + replayPointer(t, s, "canary/x", legacy) |
| + var out bytes.Buffer |
| + withCapturedStdout(t, &out, func() { _ = Pass(ctx, reg, s, pub, opt, nil, WithDigestRatchet(r)) }) |
| + if v, _ := LiveVersion(opt, "a.example.com"); v != "v2" { |
| + t.Fatalf("live %q: the box took a legacy pointer after seeing digests", v) |
| + } |
| + if !strings.Contains(out.String(), "channel pointer refused") { |
| + t.Fatalf("no refusal line:\n%s", out.String()) |
| + } |
| +} |
| + |
| +// The default is today: no marker, no ratchet, legacy pointers adopt. |
| +func TestALegacyPointerAdoptsAsBefore(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "a.example.com", "km", "canary/x", false) |
| + if err := Pass(ctx, reg, s, pub, opt, nil, WithDigestRatchet(NewDigestRatchet(t.TempDir(), nil, false))); err != nil { |
| + t.Fatal(err) |
| + } |
| + if v, _ := LiveVersion(opt, "a.example.com"); v != "v1" { |
| + t.Fatalf("live %q, want v1", v) |
| + } |
| +} |
| + |
| +// P10: the live version's capability sibling is bound to the digest too. A |
| +// bucket manifest that no longer hashes to the pointer's digest is not |
| +// written beside the running binary. |
| +func TestBackfillOfTheLiveSiblingIsBoundToTheDigest(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + markerOn(t, s, priv, "bab") |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "canary/x", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "a.example.com", "km", "canary/x", false) |
| + if err := Pass(ctx, reg, s, pub, opt, nil); err != nil { |
| + t.Fatal(err) |
| + } |
| + if v, _ := LiveVersion(opt, "a.example.com"); v != "v1" { |
| + t.Fatalf("live %q, want v1", v) |
| + } |
| + sib := StagedManifestPath(opt, "bab", "v1", "km") |
| + if err := os.Remove(sib); err != nil { |
| + t.Fatal(err) |
| + } |
| + raw, etag, err := s.Get(ctx, ManifestKey("bab", "km", "v1")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := s.Put(ctx, ManifestKey("bab", "km", "v1"), append(raw, '\n'), etag); err != nil { |
| + t.Fatal(err) |
| + } |
| + var out bytes.Buffer |
| + withCapturedStdout(t, &out, func() { |
| + if err := Pass(ctx, reg, s, pub, opt, nil); err != nil { |
| + t.Fatalf("Pass: %v", err) |
| + } |
| + }) |
| + if _, err := os.Stat(sib); err == nil { |
| + t.Fatal("a sibling that does not hash to the pointer's digest was written beside the live version") |
| + } |
| + if !strings.Contains(out.String(), "pointer signed") { |
| + t.Fatalf("no refusal line:\n%s", out.String()) |
| + } |
| +} |