From c0510fb76c20185493c1d70c404cd15aa97bc69b Mon Sep 17 00:00:00 2001 From: James Carnegie Date: Mon, 30 Sep 2024 20:53:13 +0100 Subject: [PATCH] Support images as well as indexes in ImageDetailResolvers (#183) * build: Generate test data for unsigned and no provenance image indexes * feat: Add function to build index without SBOM or provenance for linux/amd64 platform * feat: add build_image function to build image without SBOM or provenance for linux/amd64 * feat: Rename NO_SBOM_NO_PROVENANCE_INDEX_DIR to UNSIGNED_IMAGE_DIR * feat: support images in details resolvers --- attestation/attestation_test.go | 2 +- .../example_attestation_manifest_test.go | 2 +- attestation/layout.go | 44 ++++++++++++------ attestation/layout_test.go | 16 ++++++- attestation/mock.go | 3 +- attestation/referrers_test.go | 8 ++-- attestation/registry_test.go | 12 ++++- attestation/sign_test.go | 2 +- internal/test/test.go | 16 +++++-- oci/authn_test.go | 2 +- oci/oci_test.go | 6 +-- oci/output_test.go | 4 +- policy/policy_test.go | 6 ++- scripts/gen-testdata.sh | 43 ++++++++++------- sign_test.go | 6 +-- ...b6e13bcbafb8e0dc0adc0443f1a25666f9518c5071 | 1 - ...9fbabb4464fabd6dd32e82c67ea2b2a3c4e8bacdf5 | 1 - test/testdata/no-provenance-image/index.json | 1 - ...e59fcd3ddec1a637fc40fb7c83c432c6ff27e2f91} | 4 +- ...4df943785a7ba9cee12dbf446d02488319a5fbf336 | Bin 0 -> 116 bytes ...15a63e291659fbcc6c3caf3be39e437d8344b520e} | 4 +- ...df2046ce21a9e012e4030305cea97ad3f07f81a4a} | 12 ++--- ...32a96be8422c6799683f0e1e6d71de5c03a82ab35e | 1 + ...8763868b7d1bfec2331a2574a438ef44c92c0c46d} | 6 +-- ...dc0ed7eed83d5394a6902bb7bfae8fa707c2e5c33} | 2 +- ...4874a98ac3f68884996c79907f6525e3045f75390} | 6 +-- ...faf4410262dfcb0d49fdfb60698775f7bc10aad7fb | 1 + ...7de579554426777d6dc0e4206a6f746afb4ee5237e | 1 + ...b8c1f622d14aae81643a90be5ec38c9be4bdd70f6c | 1 + ...54ac672c38029a3f1a4b4acce062bd2f5a923ffae} | 2 +- test/testdata/no-provenance-index/index.json | 1 + .../oci-layout | 0 ...4df943785a7ba9cee12dbf446d02488319a5fbf336 | Bin 0 -> 116 bytes ...32a96be8422c6799683f0e1e6d71de5c03a82ab35e | 1 + ...64874a98ac3f68884996c79907f6525e3045f75390 | 16 +++++++ test/testdata/unsigned-image/index.json | 1 + .../oci-layout | 0 ...383582e5ef981b8a6bb7415d07d8d70c90d8cfd326 | 0 ...27aa6c918c390c373294ec4fc48f2c6fe703fcc6fe | 0 ...08db8fcbbd5d8ec68cf0047f954133e76d8e73d71c | 0 ...8f9fb2003318350a8026ea082b63a249cfa60918a3 | 0 ...16d96cbbdb6493e32d4f6abd8f7a191990e8efb289 | 0 ...d70a787aac618a41d4c8ec8d6e12bd12d0cc601706 | 0 ...d17ec2ce917fc9a500aab72f813d26fed8404e7162 | 0 ...6b5853ea559248fdb4ab711bcea34b65c62f0e026b | 0 ...435830fbf13cf3ab1ae27ec91246b280514e6a7b33 | 0 ...95083e64d3764c798507596ceded776c4ab038c224 | 0 ...0cd7813f814e2baad70141a3e315b7c3476b0f476f | 0 ...eb1f2c06af73879adba0fcb4743339c9a54b377635 | 0 ...976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e | 0 ...d02b74310803129c3eb1e22d2e62279f8c72340b55 | 0 ...20844157f4b0c8a6445d220af741e9fee8099bf532 | 0 ...d73212578bb3a02e8c0da7fc175c79045e73519375 | 0 ...c8e269e78c3c95df611b44580426c384d3f5057776 | 0 ...461575c51e4a626a091dc2842b35cac18c787ff80f | 0 ...dd621f59e0d43a3b346f34c34eb58da31f00a9b0ad | Bin ...4c5538aec56315874a6db860fbf6874cd7a830e3c8 | 0 ...c7db8e7affc05fd9bf98eb027038b7daf176861e85 | 0 ...fd4ef5ce285f0aa928d2651f7ec3d5a78276249dec | 0 ...86ac5c2354a573ea041b8846409c4fc0f8c4a70850 | 0 ...99438518217417e01414d18189a3cf71c07f2a02c3 | 0 ...162abec6f8bee4c463103161ab772c774e7ae9dd6d | 0 ...6b61fbf88d9287a936b285a8b4dde8893a1f4ffedf | 0 ...06b1340c501e59376a658b14b53c1828924c0ac668 | 0 ...cd975b5752cf0acaedd668bb525fcd40c3587cc460 | 0 ...f317fbeba28f5e06f1ce4d3895b3b8770140280a2e | 0 ...273e2a9c96bfe291662f08e2860e868eef69c34620 | 0 ...73269ecfac61e8dcdad3a4a643dcb577522492f898 | 0 ...f217f22bb1e106efd5ee791640411764e1cf39ea2c | 0 ...fac8f20e3645a45370e52abf9581dd4eedd152fce0 | 0 ...1165631fda0cfe691a383e7b333269a53bf9a79c34 | 0 ...c49c6440fcd6d565a8658141914a8a07c127e00d7e | 0 .../index.json | 0 test/testdata/unsigned-index/oci-layout | 1 + ...27aa6c918c390c373294ec4fc48f2c6fe703fcc6fe | 1 - ...dd621f59e0d43a3b346f34c34eb58da31f00a9b0ad | Bin 116 -> 0 bytes ...cd975b5752cf0acaedd668bb525fcd40c3587cc460 | 1 - verify_test.go | 8 ++-- 78 files changed, 159 insertions(+), 86 deletions(-) delete mode 100644 test/testdata/no-provenance-image/blobs/sha256/2e82727457f04f320b643cb6e13bcbafb8e0dc0adc0443f1a25666f9518c5071 delete mode 100644 test/testdata/no-provenance-image/blobs/sha256/b6ef78de3633e45d1c08019fbabb4464fabd6dd32e82c67ea2b2a3c4e8bacdf5 delete mode 100644 test/testdata/no-provenance-image/index.json rename test/testdata/{no-provenance-image/blobs/sha256/2aaebbb079957470e7c0adddbb054b2b4c01f717d408efba753da2bf6e8905da => no-provenance-index/blobs/sha256/059eea09507d0f904b8892ee59fcd3ddec1a637fc40fb7c83c432c6ff27e2f91} (67%) create mode 100644 test/testdata/no-provenance-index/blobs/sha256/07d9a868932bd092fa0a4c4df943785a7ba9cee12dbf446d02488319a5fbf336 rename test/testdata/{no-provenance-image/blobs/sha256/9b009d6b84b1ed941070b3f919823446286a674ad669d0baa8ab2c358aeb3a82 => no-provenance-index/blobs/sha256/0b1ee0f360b073d2f76ceed15a63e291659fbcc6c3caf3be39e437d8344b520e} (67%) rename test/testdata/{no-provenance-image/blobs/sha256/1effe3a77c594e579388dc4553dbbe762e4457a099ab8b706e67f5f9fc934701 => no-provenance-index/blobs/sha256/1e3839ac14fba8c5e4db574df2046ce21a9e012e4030305cea97ad3f07f81a4a} (63%) create mode 100644 test/testdata/no-provenance-index/blobs/sha256/363133d587b90ff7a21f7b32a96be8422c6799683f0e1e6d71de5c03a82ab35e rename test/testdata/{unsigned-test-image/blobs/sha256/7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e => no-provenance-index/blobs/sha256/52f7a760b9322aa1af76d998763868b7d1bfec2331a2574a438ef44c92c0c46d} (58%) rename test/testdata/{unsigned-test-image/blobs/sha256/a9646604f9522bf59d203a86ac5c2354a573ea041b8846409c4fc0f8c4a70850 => no-provenance-index/blobs/sha256/618f1e2f903648dde23cc38dc0ed7eed83d5394a6902bb7bfae8fa707c2e5c33} (66%) rename test/testdata/{unsigned-test-image/blobs/sha256/da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620 => no-provenance-index/blobs/sha256/7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390} (58%) create mode 100644 test/testdata/no-provenance-index/blobs/sha256/816b20ea86474dcfb2906ffaf4410262dfcb0d49fdfb60698775f7bc10aad7fb create mode 100644 test/testdata/no-provenance-index/blobs/sha256/bb0ed50656ccdb2eb114407de579554426777d6dc0e4206a6f746afb4ee5237e create mode 100644 test/testdata/no-provenance-index/blobs/sha256/c0bd7799c46e00830b4d7cb8c1f622d14aae81643a90be5ec38c9be4bdd70f6c rename test/testdata/{unsigned-test-image/blobs/sha256/da5651e8877b960aa30f32f317fbeba28f5e06f1ce4d3895b3b8770140280a2e => no-provenance-index/blobs/sha256/f0dac65dd0ff6a656c419c654ac672c38029a3f1a4b4acce062bd2f5a923ffae} (66%) create mode 100644 test/testdata/no-provenance-index/index.json rename test/testdata/{no-provenance-image => no-provenance-index}/oci-layout (100%) create mode 100644 test/testdata/unsigned-image/blobs/sha256/07d9a868932bd092fa0a4c4df943785a7ba9cee12dbf446d02488319a5fbf336 create mode 100644 test/testdata/unsigned-image/blobs/sha256/363133d587b90ff7a21f7b32a96be8422c6799683f0e1e6d71de5c03a82ab35e create mode 100644 test/testdata/unsigned-image/blobs/sha256/7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390 create mode 100644 test/testdata/unsigned-image/index.json rename test/testdata/{unsigned-test-image => unsigned-image}/oci-layout (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/0f2ee9a338149a5a1435a7383582e5ef981b8a6bb7415d07d8d70c90d8cfd326 (100%) rename test/testdata/{no-provenance-image => unsigned-index}/blobs/sha256/1c70b3e7c3a57801501ec127aa6c918c390c373294ec4fc48f2c6fe703fcc6fe (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/26da286bbc886aa14d191808db8fcbbd5d8ec68cf0047f954133e76d8e73d71c (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/2953164d6cc6c8bb8271f78f9fb2003318350a8026ea082b63a249cfa60918a3 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/2a9b671f3fc9bc5ca967b616d96cbbdb6493e32d4f6abd8f7a191990e8efb289 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/371954672cfaa92735d6fbd70a787aac618a41d4c8ec8d6e12bd12d0cc601706 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/3883faf6acc3cae029364ed17ec2ce917fc9a500aab72f813d26fed8404e7162 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/3e64f9d2888ed9211fbf2c6b5853ea559248fdb4ab711bcea34b65c62f0e026b (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/4e5988d06eee647cb901d4435830fbf13cf3ab1ae27ec91246b280514e6a7b33 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/5171425b78a2aedb43eb4e95083e64d3764c798507596ceded776c4ab038c224 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/6658b8ba1e1221a6288bf50cd7813f814e2baad70141a3e315b7c3476b0f476f (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/6c3da8eeaba64ce5acfcbaeb1f2c06af73879adba0fcb4743339c9a54b377635 (100%) rename test/testdata/{no-provenance-image => unsigned-index}/blobs/sha256/7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/8049aa9ad3479085066b31d02b74310803129c3eb1e22d2e62279f8c72340b55 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/8f2f55fc493890c2482a1220844157f4b0c8a6445d220af741e9fee8099bf532 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/8f94b6e2a8be82e2e5b562d73212578bb3a02e8c0da7fc175c79045e73519375 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/92d3311aa91737ff81e2a4c8e269e78c3c95df611b44580426c384d3f5057776 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/9638ca53d2795806cf51b7461575c51e4a626a091dc2842b35cac18c787ff80f (100%) rename test/testdata/{no-provenance-image => unsigned-index}/blobs/sha256/97a548f8d65d9ab617f608dd621f59e0d43a3b346f34c34eb58da31f00a9b0ad (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/98e06f6b48edd74e21e8504c5538aec56315874a6db860fbf6874cd7a830e3c8 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/9fe102c03d71d47a24cd7fc7db8e7affc05fd9bf98eb027038b7daf176861e85 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/a4cf4b24f3fa8cd49a59e8fd4ef5ce285f0aa928d2651f7ec3d5a78276249dec (100%) rename test/testdata/{no-provenance-image => unsigned-index}/blobs/sha256/a9646604f9522bf59d203a86ac5c2354a573ea041b8846409c4fc0f8c4a70850 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/aeca14119e3242c51633a899438518217417e01414d18189a3cf71c07f2a02c3 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/c01e5307ec84299048d76f162abec6f8bee4c463103161ab772c774e7ae9dd6d (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/c6dd08ccc92ab60a87648a6b61fbf88d9287a936b285a8b4dde8893a1f4ffedf (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/c9f436179969b60ec0bbd406b1340c501e59376a658b14b53c1828924c0ac668 (100%) rename test/testdata/{no-provenance-image => unsigned-index}/blobs/sha256/d85d624a324422194b43cccd975b5752cf0acaedd668bb525fcd40c3587cc460 (100%) rename test/testdata/{no-provenance-image => unsigned-index}/blobs/sha256/da5651e8877b960aa30f32f317fbeba28f5e06f1ce4d3895b3b8770140280a2e (100%) rename test/testdata/{no-provenance-image => unsigned-index}/blobs/sha256/da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/db8f2a6e112ea6396f57d073269ecfac61e8dcdad3a4a643dcb577522492f898 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/e0a9b9404ac2691b9b1c9ef217f22bb1e106efd5ee791640411764e1cf39ea2c (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/f2b95cecafef9c22a5d059fac8f20e3645a45370e52abf9581dd4eedd152fce0 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/f634c4c53b03bf8ff917b61165631fda0cfe691a383e7b333269a53bf9a79c34 (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/blobs/sha256/fed2c8841731e2cf1ceb53c49c6440fcd6d565a8658141914a8a07c127e00d7e (100%) rename test/testdata/{unsigned-test-image => unsigned-index}/index.json (100%) create mode 100644 test/testdata/unsigned-index/oci-layout delete mode 100644 test/testdata/unsigned-test-image/blobs/sha256/1c70b3e7c3a57801501ec127aa6c918c390c373294ec4fc48f2c6fe703fcc6fe delete mode 100644 test/testdata/unsigned-test-image/blobs/sha256/97a548f8d65d9ab617f608dd621f59e0d43a3b346f34c34eb58da31f00a9b0ad delete mode 100644 test/testdata/unsigned-test-image/blobs/sha256/d85d624a324422194b43cccd975b5752cf0acaedd668bb525fcd40c3587cc460 diff --git a/attestation/attestation_test.go b/attestation/attestation_test.go index 5ebc55b..362b3ab 100644 --- a/attestation/attestation_test.go +++ b/attestation/attestation_test.go @@ -12,7 +12,7 @@ import ( const ExpectedStatements = 4 func TestExtractAnnotatedStatements(t *testing.T) { - statements, err := attestation.ExtractAnnotatedStatements(test.UnsignedTestImage(".."), intoto.PayloadType) + statements, err := attestation.ExtractAnnotatedStatements(test.UnsignedTestIndex(".."), intoto.PayloadType) assert.NoError(t, err) assert.Equalf(t, len(statements), ExpectedStatements, "expected %d statement, got %d", ExpectedStatements, len(statements)) } diff --git a/attestation/example_attestation_manifest_test.go b/attestation/example_attestation_manifest_test.go index 1612f93..a8e0ece 100644 --- a/attestation/example_attestation_manifest_test.go +++ b/attestation/example_attestation_manifest_test.go @@ -31,7 +31,7 @@ func ExampleManifest() { ref := "docker/image-signer-verifier:latest" - digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") + digest, err := v1.NewHash("sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390") if err != nil { panic(err) } diff --git a/attestation/layout.go b/attestation/layout.go index b68ffdf..24aaa7b 100644 --- a/attestation/layout.go +++ b/attestation/layout.go @@ -14,6 +14,9 @@ import ( ) // implementation of Resolver that closes over attestations from an oci layout. + +var _ Resolver = (*LayoutResolver)(nil) + type LayoutResolver struct { *Manifest *oci.ImageSpec @@ -86,38 +89,49 @@ func (r *LayoutResolver) ImagePlatform(_ context.Context) (*v1.Platform, error) } func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error) { - idx, err := layout.ImageIndexFromPath(path) + layoutIndex, err := layout.ImageIndexFromPath(path) if err != nil { return nil, err } - idxm, err := idx.IndexManifest() + layoutIndexManifest, err := layoutIndex.IndexManifest() if err != nil { return nil, fmt.Errorf("failed to get digest: %w", err) } - idxDescriptor := idxm.Manifests[0] - idxDigest := idxDescriptor.Digest - subjectName := idxDescriptor.Annotations[ocispec.AnnotationRefName] + layoutDescriptor := layoutIndexManifest.Manifests[0] + layoutDescriptorDigest := layoutDescriptor.Digest + subjectName := layoutDescriptor.Annotations[ocispec.AnnotationRefName] if _, err := reference.ParseNamed(subjectName); err != nil { // try the containerd annotation if the org.opencontainers.image.ref.name is not a full name - subjectName = idxDescriptor.Annotations[containerd.AnnotationImageName] + subjectName = layoutDescriptor.Annotations[containerd.AnnotationImageName] if _, err := reference.ParseNamed(subjectName); err != nil { return nil, fmt.Errorf("failed to find subject name in annotations") } } - mfs, err := idx.ImageIndex(idxDigest) - if err != nil { - return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err) + // check if digest refers to an image or an index + _, err = layoutIndex.Image(layoutDescriptorDigest) + if err == nil { + return &Manifest{ + OriginalLayers: nil, + OriginalDescriptor: nil, + SubjectName: subjectName, + SubjectDescriptor: &layoutDescriptor, + }, nil } - mfs2, err := mfs.IndexManifest() + + subjectIndex, err := layoutIndex.ImageIndex(layoutDescriptorDigest) + if err != nil { + return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", layoutDescriptorDigest.String(), err) + } + subjectIndexManifest, err := subjectIndex.IndexManifest() if err != nil { return nil, fmt.Errorf("failed to extract IndexManifest from ImageIndex: %w", err) } var subjectDescriptor *v1.Descriptor - for i := range mfs2.Manifests { - manifest := &mfs2.Manifests[i] + for i := range subjectIndexManifest.Manifests { + manifest := &subjectIndexManifest.Manifests[i] if manifest.Platform != nil { if manifest.Platform.Equals(*platform) { subjectDescriptor = manifest @@ -128,8 +142,8 @@ func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error if subjectDescriptor == nil { return nil, fmt.Errorf("platform not found in index") } - for i := range mfs2.Manifests { - mf := &mfs2.Manifests[i] + for i := range subjectIndexManifest.Manifests { + mf := &subjectIndexManifest.Manifests[i] if mf.Annotations[DockerReferenceType] != AttestationManifestType { continue } @@ -138,7 +152,7 @@ func manifestFromOCILayout(path string, platform *v1.Platform) (*Manifest, error continue } - attestationImage, err := mfs.Image(mf.Digest) + attestationImage, err := subjectIndex.Image(mf.Digest) if err != nil { return nil, fmt.Errorf("failed to extract attestation image with digest %s: %w", mf.Digest.String(), err) } diff --git a/attestation/layout_test.go b/attestation/layout_test.go index 3aaaf9f..4be30fe 100644 --- a/attestation/layout_test.go +++ b/attestation/layout_test.go @@ -1,6 +1,7 @@ package attestation_test import ( + "context" "path/filepath" "strings" "testing" @@ -25,7 +26,7 @@ func TestAttestationFromOCILayout(t *testing.T) { } opts := &attestation.SigningOptions{} - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts) require.NoError(t, err) @@ -74,7 +75,7 @@ func TestSubjectNameAnnotations(t *testing.T) { ociLayoutPath string errorStr string }{ - {name: "oci annotation", ociLayoutPath: test.UnsignedTestImage("..")}, + {name: "oci annotation", ociLayoutPath: test.UnsignedTestIndex("..")}, {name: "containerd annotation", ociLayoutPath: filepath.Join("..", "test", "testdata", "containerd-subject-layout")}, {name: "missing subject name", ociLayoutPath: filepath.Join("..", "test", "testdata", "missing-subject-layout"), errorStr: "failed to find subject name in annotations"}, } @@ -93,3 +94,14 @@ func TestSubjectNameAnnotations(t *testing.T) { }) } } + +func TestImageDetailsFromImageLayout(t *testing.T) { + spec, err := oci.ParseImageSpec(oci.LocalPrefix+test.UnsignedTestImage(".."), oci.WithPlatform("linux/arm64")) + require.NoError(t, err) + resolver, err := policy.CreateImageDetailsResolver(spec) + require.NoError(t, err) + desc, err := resolver.ImageDescriptor(context.Background()) + require.NoError(t, err) + digest := desc.Digest.String() + assert.Equal(t, "sha256:7ae6b41655929ad8e1848064874a98ac3f68884996c79907f6525e3045f75390", digest) +} diff --git a/attestation/mock.go b/attestation/mock.go index 9fb8401..977e087 100644 --- a/attestation/mock.go +++ b/attestation/mock.go @@ -3,6 +3,7 @@ package attestation import ( "context" + "github.com/docker/attest/internal/test" "github.com/docker/attest/oci" v1 "github.com/google/go-containerregistry/pkg/v1" ) @@ -36,7 +37,7 @@ func (r MockResolver) ImageDescriptor(_ context.Context) (*v1.Descriptor, error) if r.DescriptorFn != nil { return r.DescriptorFn() } - digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") + digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest) if err != nil { return nil, err } diff --git a/attestation/referrers_test.go b/attestation/referrers_test.go index e6c8d39..c54d919 100644 --- a/attestation/referrers_test.go +++ b/attestation/referrers_test.go @@ -89,7 +89,7 @@ func TestAttestationReferenceTypes(t *testing.T) { require.NoError(t, err) opts := &attestation.SigningOptions{} - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) indexName := fmt.Sprintf("%s/repo:root", u.Host) @@ -209,7 +209,7 @@ func TestReferencesInDifferentRepo(t *testing.T) { require.NoError(t, err) opts := &attestation.SigningOptions{} - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName) @@ -233,7 +233,7 @@ func TestReferencesInDifferentRepo(t *testing.T) { require.NoError(t, err) opts := &attestation.SigningOptions{} - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName) @@ -286,7 +286,7 @@ func TestCorrectArtifactTypeInTagFallback(t *testing.T) { repoName := "repo" opts := &attestation.SigningOptions{} - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) indexName := fmt.Sprintf("%s/%s:latest", serverURL.Host, repoName) diff --git a/attestation/registry_test.go b/attestation/registry_test.go index 15d37a2..dbcb128 100644 --- a/attestation/registry_test.go +++ b/attestation/registry_test.go @@ -24,7 +24,7 @@ func TestRegistry(t *testing.T) { require.NoError(t, err) opts := &attestation.SigningOptions{} - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) signedManifests, err := attest.SignStatements(ctx, attIdx.Index, signer, opts) require.NoError(t, err) @@ -46,4 +46,14 @@ func TestRegistry(t *testing.T) { require.NoError(t, err) digest := desc.Digest.String() assert.True(t, strings.Contains(digest, "sha256:")) + + // resolver also works with platform specific digest + spec, err = oci.ParseImageSpec(fmt.Sprintf("%s@%s", indexName, digest)) + require.NoError(t, err) + + resolver, err = policy.CreateImageDetailsResolver(spec) + require.NoError(t, err) + desc, err = resolver.ImageDescriptor(ctx) + require.NoError(t, err) + assert.Equal(t, desc.Digest.String(), digest) } diff --git a/attestation/sign_test.go b/attestation/sign_test.go index 90a686a..690998f 100644 --- a/attestation/sign_test.go +++ b/attestation/sign_test.go @@ -249,7 +249,7 @@ func TestSimpleStatementSigning(t *testing.T) { PredicateType: attestation.VSAPredicateType, }, } - digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") + digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest) require.NoError(t, err) subject := &v1.Descriptor{ MediaType: "application/vnd.oci.image.manifest.v1+json", diff --git a/internal/test/test.go b/internal/test/test.go index 7ab6186..dbc2cbf 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -23,14 +23,20 @@ import ( ) const ( - UseMockKMS = true - - AWSRegion = "us-east-1" - AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox + UseMockKMS = true + AWSRegion = "us-east-1" + AWSKMSKeyARN = "arn:aws:kms:us-east-1:175142243308:alias/doi-signing" // sandbox + UnsignedLinuxAMD64ImageDigest = "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620" + UnsignedLinuxArm64ImageDigest = "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e" ) +func UnsignedTestIndex(rel ...string) string { + rel = append(rel, "test", "testdata", "unsigned-index") + return filepath.Join(rel...) +} + func UnsignedTestImage(rel ...string) string { - rel = append(rel, "test", "testdata", "unsigned-test-image") + rel = append(rel, "test", "testdata", "unsigned-image") return filepath.Join(rel...) } diff --git a/oci/authn_test.go b/oci/authn_test.go index 0803f10..e415d7a 100644 --- a/oci/authn_test.go +++ b/oci/authn_test.go @@ -12,7 +12,7 @@ import ( ) func TestRegistryAuth(t *testing.T) { - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) // test cases for ecr, gcr and dockerhub testCases := []struct { diff --git a/oci/oci_test.go b/oci/oci_test.go index ec4a4b1..65766d7 100644 --- a/oci/oci_test.go +++ b/oci/oci_test.go @@ -67,7 +67,7 @@ func TestRefToPurl(t *testing.T) { // Test fix for https://github.com/docker/secure-artifacts-team-issues/issues/202 func TestImageDigestForPlatform(t *testing.T) { - idx, err := layout.ImageIndexFromPath(test.UnsignedTestImage("..")) + idx, err := layout.ImageIndexFromPath(test.UnsignedTestIndex("..")) assert.NoError(t, err) idxm, err := idx.IndexManifest() @@ -86,14 +86,14 @@ func TestImageDigestForPlatform(t *testing.T) { desc, err := oci.ImageDescriptor(mfs2, p) assert.NoError(t, err) digest := desc.Digest.String() - assert.Equal(t, "sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620", digest) + assert.Equal(t, test.UnsignedLinuxAMD64ImageDigest, digest) p, err = oci.ParsePlatform("linux/arm64") assert.NoError(t, err) desc, err = oci.ImageDescriptor(mfs2, p) assert.NoError(t, err) digest = desc.Digest.String() - assert.Equal(t, "sha256:7a76cec943853f9f7105b1976afa1bf7cd5bb6afc4e9d5852dd8da7cf81ae86e", digest) + assert.Equal(t, test.UnsignedLinuxArm64ImageDigest, digest) } func TestWithoutTag(t *testing.T) { diff --git a/oci/output_test.go b/oci/output_test.go index 5b4fc24..e313836 100644 --- a/oci/output_test.go +++ b/oci/output_test.go @@ -18,7 +18,7 @@ import ( func TestSavingIndex(t *testing.T) { outputLayout := test.CreateTempDir(t, "", "mirror-test") - attIdx, err := oci.IndexFromPath(test.UnsignedTestImage("..")) + attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex("..")) require.NoError(t, err) ctx := context.Background() @@ -73,7 +73,7 @@ func TestSavingReferrers(t *testing.T) { }, } - digest, err := v1.NewHash("sha256:da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620") + digest, err := v1.NewHash(test.UnsignedLinuxAMD64ImageDigest) require.NoError(t, err) subject := &v1.Descriptor{ MediaType: "application/vnd.oci.image.manifest.v1+json", diff --git a/policy/policy_test.go b/policy/policy_test.go index 57c4dde..455e447 100644 --- a/policy/policy_test.go +++ b/policy/policy_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/docker/attest/attestation" @@ -357,6 +358,7 @@ func TestVerifySubject(t *testing.T) { }, } + digestHex := strings.TrimPrefix(test.UnsignedLinuxAMD64ImageDigest, "sha256:") for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { defaultResolver.Image = tc.img @@ -365,7 +367,7 @@ func TestVerifySubject(t *testing.T) { return &v1.Platform{Architecture: "amd64", OS: "linux"}, nil } // digest from mock resolver - tc.subject[0].Digest = map[string]string{"sha256": "da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"} + tc.subject[0].Digest = map[string]string{"sha256": digestHex} if tc.digest != "" { tc.subject[0].Digest = map[string]string{"sha256": tc.digest} } @@ -381,7 +383,7 @@ func TestVerifySubject(t *testing.T) { subject := []intoto.Subject{ { Name: "pkg:docker/alpine@latest?platform=linux%2Famd64", - Digest: map[string]string{"sha256": "da8b190665956ea07890a0273e2a9c96bfe291662f08e2860e868eef69c34620"}, + Digest: map[string]string{"sha256": digestHex}, }, } diff --git a/scripts/gen-testdata.sh b/scripts/gen-testdata.sh index 815f1c1..47621af 100755 --- a/scripts/gen-testdata.sh +++ b/scripts/gen-testdata.sh @@ -10,36 +10,45 @@ function check_command () { function cleanup_testdata () { echo "Cleaning up existing testdata..." + rm -rf "${TESTDATA_PATH:?}/${UNSIGNED_INDEX_DIR:?}" + rm -rf "${TESTDATA_PATH:?}/${NO_PROVENANCE_INDEX_DIR:?}" rm -rf "${TESTDATA_PATH:?}/${UNSIGNED_IMAGE_DIR:?}" - rm -rf "${TESTDATA_PATH:?}/${NO_PROVENANCE_IMAGE_DIR:?}" } -function build_unsigned_image () { +function build_unsigned_index () { + echo "Building $UNSIGNED_INDEX_DIR..." + docker buildx build "$TEST_INDEX_DOCKERFILE_PATH" --sbom true --provenance true --platform linux/amd64,linux/arm64 \ + --output type=oci,tar=false,name="$TEST_INDEX_REPO:$TEST_INDEX_TAG",dest="$TESTDATA_PATH/$UNSIGNED_INDEX_DIR" +} + +function build_no_provenance_index () { + echo "Building unsigned $NO_PROVENANCE_INDEX_DIR..." + docker buildx build "$TEST_INDEX_DOCKERFILE_PATH" --sbom true --provenance false --platform linux/amd64,linux/arm64 \ + --output type=oci,tar=false,name="$TEST_INDEX_REPO:$TEST_INDEX_TAG",dest="$TESTDATA_PATH/$NO_PROVENANCE_INDEX_DIR" +} + +function build_image () { echo "Building $UNSIGNED_IMAGE_DIR..." - docker buildx build "$TEST_IMAGE_DOCKERFILE_PATH" --sbom true --provenance true --platform linux/amd64,linux/arm64 \ - --output type=oci,tar=false,name="$TEST_IMAGE_REPO:$TEST_IMAGE_TAG",dest="$TESTDATA_PATH/$UNSIGNED_IMAGE_DIR" -} - -function build_no_provenance_image () { - echo "Building unsigned $NO_PROVENANCE_IMAGE_DIR..." - docker buildx build "$TEST_IMAGE_DOCKERFILE_PATH" --sbom true --provenance false --platform linux/amd64,linux/arm64 \ - --output type=oci,tar=false,name="$TEST_IMAGE_REPO:$TEST_IMAGE_TAG",dest="$TESTDATA_PATH/$NO_PROVENANCE_IMAGE_DIR" + docker buildx build "$TEST_INDEX_DOCKERFILE_PATH" --sbom false --provenance false --platform linux/amd64 \ + --output type=oci,tar=false,name="$TEST_INDEX_REPO:$TEST_INDEX_TAG",dest="$TESTDATA_PATH/$UNSIGNED_IMAGE_DIR" } # Check required commands check_command docker TESTDATA_PATH="../test/testdata" -TEST_IMAGE_DOCKERFILE_PATH="../test" -TEST_IMAGE_REPO="test-image" -TEST_IMAGE_TAG="test" -UNSIGNED_IMAGE_DIR="unsigned-test-image" -NO_PROVENANCE_IMAGE_DIR="no-provenance-image" +TEST_INDEX_DOCKERFILE_PATH="../test" +TEST_INDEX_REPO="test-image" +TEST_INDEX_TAG="test" +UNSIGNED_INDEX_DIR="unsigned-index" +NO_PROVENANCE_INDEX_DIR="no-provenance-index" +UNSIGNED_IMAGE_DIR="unsigned-image" ATTESTATION_PAYLOADTYPE="application/vnd.in-toto+json" # Run steps cleanup_testdata -build_unsigned_image -build_no_provenance_image +build_unsigned_index +build_no_provenance_index +build_image echo "Process completed successfully." diff --git a/sign_test.go b/sign_test.go index 81c6d9a..14c1e6a 100644 --- a/sign_test.go +++ b/sign_test.go @@ -15,7 +15,7 @@ import ( ) var ( - NoProvenanceImage = filepath.Join("test", "testdata", "no-provenance-image") + NoProvenanceImage = filepath.Join("test", "testdata", "no-provenance-index") PassPolicyDir = filepath.Join("test", "testdata", "local-policy-pass") PassMirrorPolicyDir = filepath.Join("test", "testdata", "local-policy-mirror") PassNoTLPolicyDir = filepath.Join("test", "testdata", "local-policy-no-tl") @@ -35,8 +35,8 @@ func TestSignVerifyOCILayout(t *testing.T) { expectedAttestations int replace bool }{ - {"signed replaced", test.UnsignedTestImage(), 0, 4, true}, - {"without replace", test.UnsignedTestImage(), 4, 4, false}, + {"signed replaced", test.UnsignedTestIndex(), 0, 4, true}, + {"without replace", test.UnsignedTestIndex(), 4, 4, false}, // image without provenance doesn't fail {"no provenance (replace)", NoProvenanceImage, 0, 2, true}, {"no provenance (no replace)", NoProvenanceImage, 2, 2, false}, diff --git a/test/testdata/no-provenance-image/blobs/sha256/2e82727457f04f320b643cb6e13bcbafb8e0dc0adc0443f1a25666f9518c5071 b/test/testdata/no-provenance-image/blobs/sha256/2e82727457f04f320b643cb6e13bcbafb8e0dc0adc0443f1a25666f9518c5071 deleted file mode 100644 index 49c9cbe..0000000 --- a/test/testdata/no-provenance-image/blobs/sha256/2e82727457f04f320b643cb6e13bcbafb8e0dc0adc0443f1a25666f9518c5071 +++ /dev/null @@ -1 +0,0 @@ -{"architecture":"unknown","os":"unknown","config":{},"rootfs":{"type":"layers","diff_ids":["sha256:da5651e8877b960aa30f32f317fbeba28f5e06f1ce4d3895b3b8770140280a2e"]}} \ No newline at end of file diff --git a/test/testdata/no-provenance-image/blobs/sha256/b6ef78de3633e45d1c08019fbabb4464fabd6dd32e82c67ea2b2a3c4e8bacdf5 b/test/testdata/no-provenance-image/blobs/sha256/b6ef78de3633e45d1c08019fbabb4464fabd6dd32e82c67ea2b2a3c4e8bacdf5 deleted file mode 100644 index 38c4ab1..0000000 --- a/test/testdata/no-provenance-image/blobs/sha256/b6ef78de3633e45d1c08019fbabb4464fabd6dd32e82c67ea2b2a3c4e8bacdf5 +++ /dev/null @@ -1 +0,0 @@ -{"architecture":"unknown","os":"unknown","config":{},"rootfs":{"type":"layers","diff_ids":["sha256:a9646604f9522bf59d203a86ac5c2354a573ea041b8846409c4fc0f8c4a70850"]}} \ No newline at end of file diff --git a/test/testdata/no-provenance-image/index.json b/test/testdata/no-provenance-image/index.json deleted file mode 100644 index 09786f4..0000000 --- a/test/testdata/no-provenance-image/index.json +++ /dev/null @@ -1 +0,0 @@ -{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:1effe3a77c594e579388dc4553dbbe762e4457a099ab8b706e67f5f9fc934701","size":1607,"annotations":{"org.opencontainers.image.created":"2024-04-29T10:23:48Z","org.opencontainers.image.ref.name":"docker.io/library/test-image:test"}}]} \ No newline at end of file diff --git a/test/testdata/no-provenance-image/blobs/sha256/2aaebbb079957470e7c0adddbb054b2b4c01f717d408efba753da2bf6e8905da b/test/testdata/no-provenance-index/blobs/sha256/059eea09507d0f904b8892ee59fcd3ddec1a637fc40fb7c83c432c6ff27e2f91 similarity index 67% rename from test/testdata/no-provenance-image/blobs/sha256/2aaebbb079957470e7c0adddbb054b2b4c01f717d408efba753da2bf6e8905da rename to test/testdata/no-provenance-index/blobs/sha256/059eea09507d0f904b8892ee59fcd3ddec1a637fc40fb7c83c432c6ff27e2f91 index da3a9d6..f1dcf9b 100644 --- a/test/testdata/no-provenance-image/blobs/sha256/2aaebbb079957470e7c0adddbb054b2b4c01f717d408efba753da2bf6e8905da +++ b/test/testdata/no-provenance-index/blobs/sha256/059eea09507d0f904b8892ee59fcd3ddec1a637fc40fb7c83c432c6ff27e2f91 @@ -3,13 +3,13 @@ "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", - "digest": "sha256:b6ef78de3633e45d1c08019fbabb4464fabd6dd32e82c67ea2b2a3c4e8bacdf5", + "digest": "sha256:bb0ed50656ccdb2eb114407de579554426777d6dc0e4206a6f746afb4ee5237e", "size": 167 }, "layers": [ { "mediaType": "application/vnd.in-toto+json", - "digest": "sha256:a9646604f9522bf59d203a86ac5c2354a573ea041b8846409c4fc0f8c4a70850", + "digest": "sha256:618f1e2f903648dde23cc38dc0ed7eed83d5394a6902bb7bfae8fa707c2e5c33", "size": 946, "annotations": { "in-toto.io/predicate-type": "https://spdx.dev/Document" diff --git a/test/testdata/no-provenance-index/blobs/sha256/07d9a868932bd092fa0a4c4df943785a7ba9cee12dbf446d02488319a5fbf336 b/test/testdata/no-provenance-index/blobs/sha256/07d9a868932bd092fa0a4c4df943785a7ba9cee12dbf446d02488319a5fbf336 new file mode 100644 index 0000000000000000000000000000000000000000..fb90f16e96804f37937d2f0f0eabeca2c35ee0df GIT binary patch literal 116 zcmb2|=3oGW|EE08o;-P7d!g?;H!)lOF)Hj&${?{+FO9r(CDU|#%`$%jlY