From 3b1b4c4874a31b91736ff7b5d8e6aa2da520fd74 Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Fri, 19 Nov 2021 15:27:54 -0800 Subject: [PATCH] Add `mutate.AttachFileTo*` for attaching SBOMs. This adds `mutate` methods for attaching SBOMs to `oci.Signed*`, so that downstream tools can start to manipulate the attachments on `oci.Signed*`. Signed-off-by: Matt Moore --- pkg/oci/mutate/mutate.go | 64 ++++++++++++++++++++++++++++++----- pkg/oci/mutate/mutate_test.go | 35 +++++++++++++++++++ 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/pkg/oci/mutate/mutate.go b/pkg/oci/mutate/mutate.go index 48ceb2c4252..2c6a3f1429d 100644 --- a/pkg/oci/mutate/mutate.go +++ b/pkg/oci/mutate/mutate.go @@ -151,29 +151,55 @@ func AttachAttestationToEntity(se oci.SignedEntity, att oci.Signature, opts ...S } } +// AttachFileToEntity attaches the provided file to the provided entity. +func AttachFileToEntity(se oci.SignedEntity, name string, f oci.File, opts ...SignOption) (oci.SignedEntity, error) { + switch obj := se.(type) { + case oci.SignedImage: + return AttachFileToImage(obj, name, f, opts...) + case oci.SignedImageIndex: + return AttachFileToImageIndex(obj, name, f, opts...) + default: + return nil, fmt.Errorf("unsupported type: %T", se) + } +} + // AttachSignatureToImage attaches the provided signature to the provided image. func AttachSignatureToImage(si oci.SignedImage, sig oci.Signature, opts ...SignOption) (oci.SignedImage, error) { return &signedImage{ SignedImage: si, sig: sig, + attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } -// AttachAttestationToImage attaches the provided signature to the provided image. +// AttachAttestationToImage attaches the provided attestation to the provided image. func AttachAttestationToImage(si oci.SignedImage, att oci.Signature, opts ...SignOption) (oci.SignedImage, error) { return &signedImage{ SignedImage: si, att: att, + attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } +// AttachFileToImage attaches the provided file to the provided image. +func AttachFileToImage(si oci.SignedImage, name string, f oci.File, opts ...SignOption) (oci.SignedImage, error) { + return &signedImage{ + SignedImage: si, + attachments: map[string]oci.File{ + name: f, + }, + so: makeSignOpts(opts...), + }, nil +} + type signedImage struct { oci.SignedImage - sig oci.Signature - att oci.Signature - so *signOpts + sig oci.Signature + att oci.Signature + so *signOpts + attachments map[string]oci.File } // Signatures implements oci.SignedImage @@ -223,7 +249,10 @@ func (si *signedImage) Attestations() (oci.Signatures, error) { // Attachment implements oci.SignedImage func (si *signedImage) Attachment(attName string) (oci.File, error) { - return nil, errors.New("unimplemented") + if f, ok := si.attachments[attName]; ok { + return f, nil + } + return nil, fmt.Errorf("attachment %q not found", attName) } // AttachSignatureToImageIndex attaches the provided signature to the provided image index. @@ -231,6 +260,7 @@ func AttachSignatureToImageIndex(sii oci.SignedImageIndex, sig oci.Signature, op return &signedImageIndex{ ociSignedImageIndex: sii, sig: sig, + attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } @@ -240,17 +270,30 @@ func AttachAttestationToImageIndex(sii oci.SignedImageIndex, att oci.Signature, return &signedImageIndex{ ociSignedImageIndex: sii, att: att, + attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } +// AttachFileToImageIndex attaches the provided file to the provided image index. +func AttachFileToImageIndex(sii oci.SignedImageIndex, name string, f oci.File, opts ...SignOption) (oci.SignedImageIndex, error) { + return &signedImageIndex{ + ociSignedImageIndex: sii, + attachments: map[string]oci.File{ + name: f, + }, + so: makeSignOpts(opts...), + }, nil +} + type ociSignedImageIndex oci.SignedImageIndex type signedImageIndex struct { ociSignedImageIndex - sig oci.Signature - att oci.Signature - so *signOpts + sig oci.Signature + att oci.Signature + so *signOpts + attachments map[string]oci.File } // Signatures implements oci.SignedImageIndex @@ -300,5 +343,8 @@ func (sii *signedImageIndex) Attestations() (oci.Signatures, error) { // Attachment implements oci.SignedImageIndex func (sii *signedImageIndex) Attachment(attName string) (oci.File, error) { - return nil, errors.New("unimplemented") + if f, ok := sii.attachments[attName]; ok { + return f, nil + } + return nil, fmt.Errorf("attachment %q not found", attName) } diff --git a/pkg/oci/mutate/mutate_test.go b/pkg/oci/mutate/mutate_test.go index 623375030e8..9e1eef4b949 100644 --- a/pkg/oci/mutate/mutate_test.go +++ b/pkg/oci/mutate/mutate_test.go @@ -16,8 +16,10 @@ package mutate import ( + "crypto/rand" "errors" "fmt" + "reflect" "testing" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -161,6 +163,39 @@ func TestSignEntity(t *testing.T) { } sii := signed.ImageIndex(ii) + t.Run("attach SBOMs", func(t *testing.T) { + for _, se := range []oci.SignedEntity{si, sii} { + want := make([]byte, 300) + rand.Read(want) + + orig, err := static.NewFile(want) + if err != nil { + t.Fatalf("static.NewFile() = %v", err) + } + se, err = AttachFileToEntity(se, "sbom", orig) + if err != nil { + t.Fatalf("AttachFileToEntity() = %v", err) + } + + f, err := se.Attachment("sbom") + if err != nil { + t.Fatalf("Attachment(sbom) = %v", err) + } + got, err := f.Payload() + if err != nil { + t.Fatalf("Payload() = %v", err) + } + if !reflect.DeepEqual(want, got) { + t.Errorf("Payload() = %v, wanted %v", got, want) + } + + f, err = se.Attachment("gitbom") + if err == nil { + t.Errorf("Attachment(gitbom) = %T, wanted error", f) + } + } + }) + t.Run("without duplicate detector (signature)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii} { orig, err := static.NewSignature(nil, "")