Skip to content

Commit dbb3017

Browse files
committed
combined tests from rebase
1 parent a9cbd67 commit dbb3017

1 file changed

Lines changed: 203 additions & 0 deletions

File tree

pkg/store/store_test.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,3 +787,206 @@ func TestAddImage_OCI11Referrers(t *testing.T) {
787787
}
788788
t.Logf("captured %d OCI referrer(s) for %s", referrerCount, baseTag.Name())
789789
}
790+
791+
// newTestRegistry starts an in-process registry and returns its host and the
792+
// remote.Option needed to talk to it over plain HTTP.
793+
func newTestRegistry(t *testing.T) (string, []remote.Option) {
794+
t.Helper()
795+
srv := httptest.NewServer(registry.New())
796+
t.Cleanup(srv.Close)
797+
host := strings.TrimPrefix(srv.URL, "http://")
798+
return host, []remote.Option{remote.WithTransport(srv.Client().Transport)}
799+
}
800+
801+
// newTestStore creates a fresh OCI layout store rooted in a temp directory.
802+
func newTestStore(t *testing.T) *store.Layout {
803+
t.Helper()
804+
s, err := store.NewLayout(t.TempDir())
805+
if err != nil {
806+
t.Fatalf("new layout: %v", err)
807+
}
808+
return s
809+
}
810+
811+
// seedImage pushes a random image to host/repo:tag and returns it, so a test
812+
// can later assert on the exact bytes/digest that were pushed.
813+
func seedImage(t *testing.T, host, repo, tag string, opts ...remote.Option) v1.Image {
814+
t.Helper()
815+
img, err := random.Image(1024, 3)
816+
if err != nil {
817+
t.Fatalf("random.Image: %v", err)
818+
}
819+
ref, err := gname.NewTag(host+"/"+repo+":"+tag, gname.Insecure)
820+
if err != nil {
821+
t.Fatalf("new tag: %v", err)
822+
}
823+
if err := remote.Write(ref, img, opts...); err != nil {
824+
t.Fatalf("remote.Write: %v", err)
825+
}
826+
return img
827+
}
828+
829+
// TestAddImagePinnedDigestIgnoresMovedTag proves the TOCTOU fix: once a caller
830+
// pins the digest it verified, a tag that moves to different content between
831+
// verification and the fetch cannot substitute its bytes into the store.
832+
func TestAddImagePinnedDigestIgnoresMovedTag(t *testing.T) {
833+
host, remoteOpts := newTestRegistry(t)
834+
original := seedImage(t, host, "test/pinned", "v1", remoteOpts...)
835+
originalDigest, err := original.Digest()
836+
if err != nil {
837+
t.Fatalf("original digest: %v", err)
838+
}
839+
840+
// Move the tag to different content, exactly as a mutable tag could be
841+
// re-pushed between verification and the pull.
842+
replacement, err := random.Image(1024, 3)
843+
if err != nil {
844+
t.Fatalf("random.Image: %v", err)
845+
}
846+
ref, err := gname.ParseReference(host + "/test/pinned:v1")
847+
if err != nil {
848+
t.Fatalf("parse: %v", err)
849+
}
850+
if err := remote.Write(ref, replacement, remoteOpts...); err != nil {
851+
t.Fatalf("remote.Write replacement: %v", err)
852+
}
853+
854+
s := newTestStore(t)
855+
got, err := s.AddImage(context.Background(), host+"/test/pinned:v1", "", true,
856+
originalDigest.String(), false, "", remoteOpts...)
857+
if err != nil {
858+
t.Fatalf("AddImage: %v", err)
859+
}
860+
if got != originalDigest.String() {
861+
t.Fatalf("stored digest = %s, want the pinned %s (the moved tag won)", got, originalDigest.String())
862+
}
863+
}
864+
865+
// TestAddImageEmptyPinResolvesTag confirms the unpinned path is untouched:
866+
// an empty pinnedDigest still resolves the tag normally.
867+
func TestAddImageEmptyPinResolvesTag(t *testing.T) {
868+
host, remoteOpts := newTestRegistry(t)
869+
img := seedImage(t, host, "test/unpinned", "v1", remoteOpts...)
870+
want, err := img.Digest()
871+
if err != nil {
872+
t.Fatalf("digest: %v", err)
873+
}
874+
875+
s := newTestStore(t)
876+
got, err := s.AddImage(context.Background(), host+"/test/unpinned:v1", "", true, "", false, "", remoteOpts...)
877+
if err != nil {
878+
t.Fatalf("AddImage: %v", err)
879+
}
880+
if got != want.String() {
881+
t.Fatalf("stored digest = %s, want %s", got, want.String())
882+
}
883+
}
884+
885+
// TestAddImage_OriginalRefAnnotation verifies that AddImage captures the original,
886+
// fully pullable containerd-style reference (registry/repo:tag) under
887+
// consts.OriginalRefAnnotation, for both single-platform images (writeImage) and
888+
// multi-platform indices (writeIndex), so that provenance survives even if the
889+
// ref/containerd-name annotations are later overwritten by a rewrite.
890+
func TestAddImage_OriginalRefAnnotation(t *testing.T) {
891+
srv := httptest.NewServer(registry.New())
892+
t.Cleanup(srv.Close)
893+
host := strings.TrimPrefix(srv.URL, "http://")
894+
895+
remoteOpts := []remote.Option{
896+
remote.WithTransport(srv.Client().Transport),
897+
}
898+
899+
t.Run("single-platform image", func(t *testing.T) {
900+
tag, err := gname.NewTag(host+"/test/image:v1", gname.Insecure)
901+
if err != nil {
902+
t.Fatalf("new tag: %v", err)
903+
}
904+
img, err := random.Image(512, 2)
905+
if err != nil {
906+
t.Fatalf("random image: %v", err)
907+
}
908+
if err := remote.Write(tag, img, remoteOpts...); err != nil {
909+
t.Fatalf("push image: %v", err)
910+
}
911+
912+
s, err := store.NewLayout(t.TempDir())
913+
if err != nil {
914+
t.Fatalf("new layout: %v", err)
915+
}
916+
if _, err := s.AddImage(context.Background(), tag.Name(), "", false, remoteOpts...); err != nil {
917+
t.Fatalf("AddImage: %v", err)
918+
}
919+
920+
wantOriginalRef := tag.Name()
921+
found := false
922+
if err := s.Walk(func(_ string, desc ocispec.Descriptor) error {
923+
if desc.Annotations[consts.OriginalRefAnnotation] == wantOriginalRef {
924+
found = true
925+
}
926+
return nil
927+
}); err != nil {
928+
t.Fatalf("Walk: %v", err)
929+
}
930+
if !found {
931+
t.Errorf("expected an artifact with OriginalRefAnnotation=%q, none found", wantOriginalRef)
932+
}
933+
})
934+
935+
t.Run("multi-platform index", func(t *testing.T) {
936+
amd64Img, err := random.Image(512, 2)
937+
if err != nil {
938+
t.Fatalf("random image amd64: %v", err)
939+
}
940+
arm64Img, err := random.Image(512, 2)
941+
if err != nil {
942+
t.Fatalf("random image arm64: %v", err)
943+
}
944+
idx := mutate.AppendManifests(
945+
empty.Index,
946+
mutate.IndexAddendum{
947+
Add: amd64Img,
948+
Descriptor: v1.Descriptor{
949+
MediaType: types.OCIManifestSchema1,
950+
Platform: &v1.Platform{OS: "linux", Architecture: "amd64"},
951+
},
952+
},
953+
mutate.IndexAddendum{
954+
Add: arm64Img,
955+
Descriptor: v1.Descriptor{
956+
MediaType: types.OCIManifestSchema1,
957+
Platform: &v1.Platform{OS: "linux", Architecture: "arm64"},
958+
},
959+
},
960+
)
961+
tag, err := gname.NewTag(host+"/test/multiarch:v1", gname.Insecure)
962+
if err != nil {
963+
t.Fatalf("new tag: %v", err)
964+
}
965+
if err := remote.WriteIndex(tag, idx, remoteOpts...); err != nil {
966+
t.Fatalf("push index: %v", err)
967+
}
968+
969+
s, err := store.NewLayout(t.TempDir())
970+
if err != nil {
971+
t.Fatalf("new layout: %v", err)
972+
}
973+
if _, err := s.AddImage(context.Background(), tag.Name(), "", false, remoteOpts...); err != nil {
974+
t.Fatalf("AddImage: %v", err)
975+
}
976+
977+
wantOriginalRef := tag.Name()
978+
found := false
979+
if err := s.Walk(func(_ string, desc ocispec.Descriptor) error {
980+
if desc.Annotations[consts.KindAnnotationName] == consts.KindAnnotationIndex &&
981+
desc.Annotations[consts.OriginalRefAnnotation] == wantOriginalRef {
982+
found = true
983+
}
984+
return nil
985+
}); err != nil {
986+
t.Fatalf("Walk: %v", err)
987+
}
988+
if !found {
989+
t.Errorf("expected the index artifact to have OriginalRefAnnotation=%q, none found", wantOriginalRef)
990+
}
991+
})
992+
}

0 commit comments

Comments
 (0)