Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions cmd/cosign/cli/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ func SaveCmd(ctx context.Context, opts options.SaveOptions, imageRef string) err
return fmt.Errorf("parsing image name %s: %w", imageRef, err)
}

// See if we are using referrers
digest, ok := ref.(name.Digest)
if ok {
indexManifest, err := ociremote.Referrers(digest, "", regClientOpts...)
if err != nil {
return fmt.Errorf("getting referrers: %w", err)
}

for _, manifest := range indexManifest.Manifests {
if manifest.ArtifactType == "" {
continue
}
artifactRef := ref.Context().Digest(manifest.Digest.String())
si, err := ociremote.SignedImage(artifactRef, regClientOpts...)
if err != nil {
return fmt.Errorf("getting signed image: %w", err)
}
return layout.WriteSignedImage(opts.Directory, si)
}
}

se, err := ociremote.SignedEntity(ref, regClientOpts...)
if err != nil {
return fmt.Errorf("signed entity: %w", err)
Expand Down
6 changes: 6 additions & 0 deletions pkg/oci/empty/signed.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type signedImage struct {
digest v1.Hash
signature oci.Signatures
attestations oci.Signatures
bundles oci.Signatures
}

func (se *signedImage) Signatures() (oci.Signatures, error) {
Expand All @@ -40,6 +41,10 @@ func (se *signedImage) Attestations() (oci.Signatures, error) {
return se.attestations, nil
}

func (se *signedImage) Bundles() (oci.Signatures, error) {
return se.bundles, nil
}

func (se *signedImage) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("no attachments")
}
Expand All @@ -66,5 +71,6 @@ func SignedImage(ref name.Reference) (oci.SignedImage, error) {
digest: d,
signature: Signatures(),
attestations: Signatures(),
bundles: Signatures(),
}, nil
}
6 changes: 6 additions & 0 deletions pkg/oci/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ type SignedEntity interface {
// Base64Signature because it's baked into the payload.
Attestations() (Signatures, error)

// Bundles returns the set of protobuf bundle attestations currently
// associated with this entity, or the empty equivalent if none are found.
// Bundles contain all the information required for verification, and are
// stored using the OCI1.1 referrer specification.
Bundles() (Signatures, error)

// Attachment returns a named entity associated with this entity, or error if not found.
Attachment(name string) (File, error)
}
24 changes: 19 additions & 5 deletions pkg/oci/layout/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/remote"
"github.com/sigstore/cosign/v3/pkg/oci/signed"
"github.com/sigstore/cosign/v3/pkg/types"
)

const (
Expand Down Expand Up @@ -59,7 +61,7 @@ var _ oci.SignedImageIndex = (*index)(nil)

// Signatures implements oci.SignedImageIndex
func (i *index) Signatures() (oci.Signatures, error) {
img, err := i.imageByAnnotation(sigsAnnotation)
img, err := i.imageByAnnotation(kindAnnotation, sigsAnnotation)
if err != nil {
return nil, err
}
Expand All @@ -71,7 +73,19 @@ func (i *index) Signatures() (oci.Signatures, error) {

// Attestations implements oci.SignedImageIndex
func (i *index) Attestations() (oci.Signatures, error) {
img, err := i.imageByAnnotation(attsAnnotation)
img, err := i.imageByAnnotation(kindAnnotation, attsAnnotation)
if err != nil {
return nil, err
}
if img == nil {
return nil, nil
}
return &sigs{img}, nil
}

// Bundles implements oci.SignedImageIndex
func (i *index) Bundles() (oci.Signatures, error) {
img, err := i.imageByAnnotation(remote.BundlePredicateType, types.CosignSignPredicateType)
if err != nil {
return nil, err
}
Expand All @@ -92,7 +106,7 @@ func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) {
var img v1.Image
var err error
if h.String() == ":" {
img, err = i.imageByAnnotation(imageAnnotation)
img, err = i.imageByAnnotation(kindAnnotation, imageAnnotation)
} else {
img, err = i.Image(h)
}
Expand All @@ -107,13 +121,13 @@ func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) {

// imageByAnnotation searches through all manifests in the index.json
// and returns the image that has the matching annotation
func (i *index) imageByAnnotation(annotation string) (v1.Image, error) {
func (i *index) imageByAnnotation(annotationKey string, annotationValue string) (v1.Image, error) {
manifest, err := i.IndexManifest()
if err != nil {
return nil, err
}
for _, m := range manifest.Manifests {
if val, ok := m.Annotations[kindAnnotation]; ok && val == annotation {
if val, ok := m.Annotations[annotationKey]; ok && val == annotationValue {
return i.Image(m.Digest)
}
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/oci/mutate/mutate.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ func (i *indexWrapper) Attestations() (oci.Signatures, error) {
return empty.Signatures(), nil
}

// Bundles implements oci.SignedImageIndex
func (i *indexWrapper) Bundles() (oci.Signatures, error) {
return empty.Signatures(), nil
}

// Attachment implements oci.SignedImage
func (*indexWrapper) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("unimplemented")
Expand Down
5 changes: 5 additions & 0 deletions pkg/oci/remote/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func (i *image) Attestations() (oci.Signatures, error) {
return attestations(i, i.opt)
}

// Bundles implements oci.SignedImage
func (i *image) Bundles() (oci.Signatures, error) {
return nil, errors.New("not implemented")
}

// Attestations implements oci.SignedImage
func (i *image) Attachment(name string) (oci.File, error) {
return attachment(i, name, i.opt)
Expand Down
5 changes: 5 additions & 0 deletions pkg/oci/remote/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func (i *index) Attestations() (oci.Signatures, error) {
return attestations(i, i.opt)
}

// Bundles implements oci.SignedImage
func (i *index) Bundles() (oci.Signatures, error) {
return nil, errors.New("not implemented")
}

// Attestations implements oci.SignedImage
func (i *index) Attachment(name string) (oci.File, error) {
return attachment(i, name, i.opt)
Expand Down
7 changes: 7 additions & 0 deletions pkg/oci/remote/unknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package remote

import (
"errors"

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sigstore/cosign/v3/pkg/oci"
Expand Down Expand Up @@ -54,6 +56,11 @@ func (i *unknown) Attestations() (oci.Signatures, error) {
return attestations(i, i.opt)
}

// Bundles implements oci.SignedEntity
func (i *unknown) Bundles() (oci.Signatures, error) {
return nil, errors.New("not implemented")
}

// Attachment implements oci.SignedEntity
func (i *unknown) Attachment(name string) (oci.File, error) {
return attachment(i, name, i.opt)
Expand Down
23 changes: 21 additions & 2 deletions pkg/oci/remote/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
goremote "github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/static"
"github.com/google/go-containerregistry/pkg/v1/types"
ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote"
Expand All @@ -49,7 +49,7 @@ func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, o
return fmt.Errorf("signed image index: %w", err)
}
if ii != nil {
if err := remote.WriteIndex(ref, ii, o.ROpt...); err != nil {
if err := goremote.WriteIndex(ref, ii, o.ROpt...); err != nil {
return fmt.Errorf("writing index: %w", err)
}
}
Expand Down Expand Up @@ -92,6 +92,25 @@ func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, o
}
return remoteWrite(attsTag, atts, o.ROpt...)
}

// write the bundles
bundles, err := sii.Bundles()
if err != nil {
return err
}
if bundles != nil { // will be nil if there are no associated bundles
if digest, ok := ref.(name.Digest); ok {
se, err := SignedEntity(ref, opts...)
if err != nil {
return err
}
err = WriteSignaturesExperimentalOCI(digest, se, opts...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not at all an expert in this part of the codebase, so I'm trying to wrap my head around how WriteSignaturesExperimentalOCI works here. Is this now needed because we were writing referring artifacts to OCI using a different API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I also had to do a lot of digging in this code base to understand what was going on.

But that's exactly right - we don't want to "just" write the protobuf bundle as an OCI blob, we also need to write an OCI manifest that (1) points at the protobuf bundle blob and (2) has a subject referring to the signed image - which is how the registry knows that this is a "referring artifact".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, thanks. So how were things working previously? Was it that we were looking up the referring artifact directly rather than going through the OCI manifest?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess to add to that, is there a change we need to make to the verification code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, so previously signatures were pushed to a tag made from the digest of the image manifest.

So if your container was example.com/myapp@sha256:abcd, then you would ask for the signature at example.com/myapp:sha256-abcd.sig (note that this is a tag, even though it looks like a digest).

The verification code shouldn't need to change. New protobuf bundle signatures should always use the OCI referrers API. But I still need to update tests 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, by previously I meant with bundles and OCI. If we were only publishing the referring artifact and not the manifest that points to the referring artifact, how was verification working? How did the OCI library know how to look up the referring artifact without a manifest?

if err != nil {
return err
}
}
}

return nil
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/oci/signed/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ func (*image) Attestations() (oci.Signatures, error) {
return empty.Signatures(), nil
}

// Bundles implements oci.SignedImage
func (*image) Bundles() (oci.Signatures, error) {
return empty.Signatures(), nil
}

// Attestations implements oci.SignedImage
func (*image) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("unimplemented")
Expand Down
5 changes: 5 additions & 0 deletions pkg/oci/signed/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ func (*index) Attestations() (oci.Signatures, error) {
return empty.Signatures(), nil
}

// Bundles implements oci.SignedImageIndex
func (*index) Bundles() (oci.Signatures, error) {
return empty.Signatures(), nil
}

// Attestations implements oci.SignedImage
func (*index) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("unimplemented")
Expand Down
Loading