Skip to content

Commit 7462213

Browse files
committed
combined tests from rebase
1 parent 15e0380 commit 7462213

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
@@ -990,3 +990,206 @@ func TestAddImage_OriginalRefAnnotation(t *testing.T) {
990990
}
991991
})
992992
}
993+
994+
// newTestRegistry starts an in-process registry and returns its host and the
995+
// remote.Option needed to talk to it over plain HTTP.
996+
func newTestRegistry(t *testing.T) (string, []remote.Option) {
997+
t.Helper()
998+
srv := httptest.NewServer(registry.New())
999+
t.Cleanup(srv.Close)
1000+
host := strings.TrimPrefix(srv.URL, "http://")
1001+
return host, []remote.Option{remote.WithTransport(srv.Client().Transport)}
1002+
}
1003+
1004+
// newTestStore creates a fresh OCI layout store rooted in a temp directory.
1005+
func newTestStore(t *testing.T) *store.Layout {
1006+
t.Helper()
1007+
s, err := store.NewLayout(t.TempDir())
1008+
if err != nil {
1009+
t.Fatalf("new layout: %v", err)
1010+
}
1011+
return s
1012+
}
1013+
1014+
// seedImage pushes a random image to host/repo:tag and returns it, so a test
1015+
// can later assert on the exact bytes/digest that were pushed.
1016+
func seedImage(t *testing.T, host, repo, tag string, opts ...remote.Option) v1.Image {
1017+
t.Helper()
1018+
img, err := random.Image(1024, 3)
1019+
if err != nil {
1020+
t.Fatalf("random.Image: %v", err)
1021+
}
1022+
ref, err := gname.NewTag(host+"/"+repo+":"+tag, gname.Insecure)
1023+
if err != nil {
1024+
t.Fatalf("new tag: %v", err)
1025+
}
1026+
if err := remote.Write(ref, img, opts...); err != nil {
1027+
t.Fatalf("remote.Write: %v", err)
1028+
}
1029+
return img
1030+
}
1031+
1032+
// TestAddImagePinnedDigestIgnoresMovedTag proves the TOCTOU fix: once a caller
1033+
// pins the digest it verified, a tag that moves to different content between
1034+
// verification and the fetch cannot substitute its bytes into the store.
1035+
func TestAddImagePinnedDigestIgnoresMovedTag(t *testing.T) {
1036+
host, remoteOpts := newTestRegistry(t)
1037+
original := seedImage(t, host, "test/pinned", "v1", remoteOpts...)
1038+
originalDigest, err := original.Digest()
1039+
if err != nil {
1040+
t.Fatalf("original digest: %v", err)
1041+
}
1042+
1043+
// Move the tag to different content, exactly as a mutable tag could be
1044+
// re-pushed between verification and the pull.
1045+
replacement, err := random.Image(1024, 3)
1046+
if err != nil {
1047+
t.Fatalf("random.Image: %v", err)
1048+
}
1049+
ref, err := gname.ParseReference(host + "/test/pinned:v1")
1050+
if err != nil {
1051+
t.Fatalf("parse: %v", err)
1052+
}
1053+
if err := remote.Write(ref, replacement, remoteOpts...); err != nil {
1054+
t.Fatalf("remote.Write replacement: %v", err)
1055+
}
1056+
1057+
s := newTestStore(t)
1058+
got, err := s.AddImage(context.Background(), host+"/test/pinned:v1", "", true,
1059+
originalDigest.String(), false, "", remoteOpts...)
1060+
if err != nil {
1061+
t.Fatalf("AddImage: %v", err)
1062+
}
1063+
if got != originalDigest.String() {
1064+
t.Fatalf("stored digest = %s, want the pinned %s (the moved tag won)", got, originalDigest.String())
1065+
}
1066+
}
1067+
1068+
// TestAddImageEmptyPinResolvesTag confirms the unpinned path is untouched:
1069+
// an empty pinnedDigest still resolves the tag normally.
1070+
func TestAddImageEmptyPinResolvesTag(t *testing.T) {
1071+
host, remoteOpts := newTestRegistry(t)
1072+
img := seedImage(t, host, "test/unpinned", "v1", remoteOpts...)
1073+
want, err := img.Digest()
1074+
if err != nil {
1075+
t.Fatalf("digest: %v", err)
1076+
}
1077+
1078+
s := newTestStore(t)
1079+
got, err := s.AddImage(context.Background(), host+"/test/unpinned:v1", "", true, "", false, "", remoteOpts...)
1080+
if err != nil {
1081+
t.Fatalf("AddImage: %v", err)
1082+
}
1083+
if got != want.String() {
1084+
t.Fatalf("stored digest = %s, want %s", got, want.String())
1085+
}
1086+
}
1087+
1088+
// TestAddImage_OriginalRefAnnotation verifies that AddImage captures the original,
1089+
// fully pullable containerd-style reference (registry/repo:tag) under
1090+
// consts.OriginalRefAnnotation, for both single-platform images (writeImage) and
1091+
// multi-platform indices (writeIndex), so that provenance survives even if the
1092+
// ref/containerd-name annotations are later overwritten by a rewrite.
1093+
func TestAddImage_OriginalRefAnnotation(t *testing.T) {
1094+
srv := httptest.NewServer(registry.New())
1095+
t.Cleanup(srv.Close)
1096+
host := strings.TrimPrefix(srv.URL, "http://")
1097+
1098+
remoteOpts := []remote.Option{
1099+
remote.WithTransport(srv.Client().Transport),
1100+
}
1101+
1102+
t.Run("single-platform image", func(t *testing.T) {
1103+
tag, err := gname.NewTag(host+"/test/image:v1", gname.Insecure)
1104+
if err != nil {
1105+
t.Fatalf("new tag: %v", err)
1106+
}
1107+
img, err := random.Image(512, 2)
1108+
if err != nil {
1109+
t.Fatalf("random image: %v", err)
1110+
}
1111+
if err := remote.Write(tag, img, remoteOpts...); err != nil {
1112+
t.Fatalf("push image: %v", err)
1113+
}
1114+
1115+
s, err := store.NewLayout(t.TempDir())
1116+
if err != nil {
1117+
t.Fatalf("new layout: %v", err)
1118+
}
1119+
if _, err := s.AddImage(context.Background(), tag.Name(), "", false, remoteOpts...); err != nil {
1120+
t.Fatalf("AddImage: %v", err)
1121+
}
1122+
1123+
wantOriginalRef := tag.Name()
1124+
found := false
1125+
if err := s.Walk(func(_ string, desc ocispec.Descriptor) error {
1126+
if desc.Annotations[consts.OriginalRefAnnotation] == wantOriginalRef {
1127+
found = true
1128+
}
1129+
return nil
1130+
}); err != nil {
1131+
t.Fatalf("Walk: %v", err)
1132+
}
1133+
if !found {
1134+
t.Errorf("expected an artifact with OriginalRefAnnotation=%q, none found", wantOriginalRef)
1135+
}
1136+
})
1137+
1138+
t.Run("multi-platform index", func(t *testing.T) {
1139+
amd64Img, err := random.Image(512, 2)
1140+
if err != nil {
1141+
t.Fatalf("random image amd64: %v", err)
1142+
}
1143+
arm64Img, err := random.Image(512, 2)
1144+
if err != nil {
1145+
t.Fatalf("random image arm64: %v", err)
1146+
}
1147+
idx := mutate.AppendManifests(
1148+
empty.Index,
1149+
mutate.IndexAddendum{
1150+
Add: amd64Img,
1151+
Descriptor: v1.Descriptor{
1152+
MediaType: types.OCIManifestSchema1,
1153+
Platform: &v1.Platform{OS: "linux", Architecture: "amd64"},
1154+
},
1155+
},
1156+
mutate.IndexAddendum{
1157+
Add: arm64Img,
1158+
Descriptor: v1.Descriptor{
1159+
MediaType: types.OCIManifestSchema1,
1160+
Platform: &v1.Platform{OS: "linux", Architecture: "arm64"},
1161+
},
1162+
},
1163+
)
1164+
tag, err := gname.NewTag(host+"/test/multiarch:v1", gname.Insecure)
1165+
if err != nil {
1166+
t.Fatalf("new tag: %v", err)
1167+
}
1168+
if err := remote.WriteIndex(tag, idx, remoteOpts...); err != nil {
1169+
t.Fatalf("push index: %v", err)
1170+
}
1171+
1172+
s, err := store.NewLayout(t.TempDir())
1173+
if err != nil {
1174+
t.Fatalf("new layout: %v", err)
1175+
}
1176+
if _, err := s.AddImage(context.Background(), tag.Name(), "", false, remoteOpts...); err != nil {
1177+
t.Fatalf("AddImage: %v", err)
1178+
}
1179+
1180+
wantOriginalRef := tag.Name()
1181+
found := false
1182+
if err := s.Walk(func(_ string, desc ocispec.Descriptor) error {
1183+
if desc.Annotations[consts.KindAnnotationName] == consts.KindAnnotationIndex &&
1184+
desc.Annotations[consts.OriginalRefAnnotation] == wantOriginalRef {
1185+
found = true
1186+
}
1187+
return nil
1188+
}); err != nil {
1189+
t.Fatalf("Walk: %v", err)
1190+
}
1191+
if !found {
1192+
t.Errorf("expected the index artifact to have OriginalRefAnnotation=%q, none found", wantOriginalRef)
1193+
}
1194+
})
1195+
}

0 commit comments

Comments
 (0)