Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have download sbom use the Attachment API. #965

Merged
merged 1 commit into from
Oct 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
38 changes: 14 additions & 24 deletions cmd/cosign/cli/download/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,43 +36,33 @@ func SBOMCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef stri
if err != nil {
return nil, err
}
ociremoteOpts = append(ociremoteOpts,
// TODO(mattmoor): This isn't really "signatures", consider shifting to
// an SBOMs accessor?
ociremote.WithSignatureSuffix(ociremote.SBOMTagSuffix))

se, err := ociremote.SignedEntity(ref, ociremoteOpts...)
if err != nil {
return nil, err
}

// TODO(mattmoor): This logic does a shallow walk, we should use `mutate.Map`
// if we want to collect all of the SBOMs attached at any level of an index.
img, err := se.Signatures()
file, err := se.Attachment("sbom")
if err != nil {
return nil, err
}
sigs, err := img.Get()

// "attach sbom" attaches a single static.NewFile
sboms := make([]string, 0, 1)

mt, err := file.FileMediaType()
if err != nil {
return nil, err
}
if len(sigs) == 0 {
return nil, fmt.Errorf("no signatures associated with %v", ref)
}

sboms := make([]string, 0, len(sigs))
for _, l := range sigs {
mt, err := l.MediaType()
if err != nil {
return nil, err
}
fmt.Fprintf(os.Stderr, "Found SBOM of media type: %s\n", mt)
sbom, err := l.Payload()
if err != nil {
return nil, err
}
sboms = append(sboms, string(sbom))
fmt.Fprintln(out, string(sbom))
fmt.Fprintf(os.Stderr, "Found SBOM of media type: %s\n", mt)
sbom, err := file.Payload()
if err != nil {
return nil, err
}

sboms = append(sboms, string(sbom))
fmt.Fprintln(out, string(sbom))

return sboms, nil
}
12 changes: 6 additions & 6 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,35 +174,35 @@ func SignCmd(ctx context.Context, ko KeyOpts, regOpts options.RegistryOptions, a
if digest, ok := ref.(name.Digest); ok && !recursive {
se, err := ociempty.SignedImage(ref)
if err != nil {
return err
return errors.Wrap(err, "accessing image")
}
err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, upload, force, dd, sv, se)
if err != nil {
return err
return errors.Wrap(err, "signing digest")
}
continue
}

se, err := ociremote.SignedEntity(ref, opts...)
if err != nil {
return err
return errors.Wrap(err, "accessing entity")
}

if err := walk.SignedEntity(ctx, se, func(ctx context.Context, se oci.SignedEntity) error {
// Get the digest for this entity in our walk.
d, err := se.(interface{ Digest() (v1.Hash, error) }).Digest()
if err != nil {
return err
return errors.Wrap(err, "computing digest")
}
digest := ref.Context().Digest(d.String())

err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, upload, force, dd, sv, se)
if err != nil {
return err
return errors.Wrap(err, "signing digest")
}
return ErrDone
}); err != nil {
return err
return errors.Wrap(err, "recursively signing")
}
}

Expand Down
9 changes: 8 additions & 1 deletion pkg/oci/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@

package oci

import "github.com/google/go-containerregistry/pkg/v1/types"

// File is a degenerate form of SignedImage that stores a single file as a v1.Layer
type File interface {
SignedImage

// TODO(mattmoor): Consider adding useful helpers.
// FileMediaType retrieves the media type of the File
FileMediaType() (types.MediaType, error)

// Payload fetches the opaque data that is being signed.
// This will always return data when there is no error.
Payload() ([]byte, error)
}
37 changes: 37 additions & 0 deletions pkg/oci/remote/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,40 @@ func TestSignedImage(t *testing.T) {
t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers)
}
}

func TestSignedImageWithAttachment(t *testing.T) {
ri := remote.Image
t.Cleanup(func() {
remoteImage = ri
})
wantLayers := int64(1) // File must have a single layer

remoteImage = func(ref name.Reference, options ...remote.Option) (v1.Image, error) {
// Only called for signature images
return random.Image(300 /* byteSize */, wantLayers)
}

ref, err := name.ParseReference("gcr.io/distroless/static:nonroot")
if err != nil {
t.Fatalf("ParseRef() = %v", err)
}

si, err := SignedImage(ref)
if err != nil {
t.Fatalf("Signatures() = %v", err)
}

file, err := si.Attachment("sbom")
if err != nil {
t.Fatalf("Signatures() = %v", err)
}

payload, err := file.Payload()
if err != nil {
t.Errorf("Payload() = %v", err)
}
// We check greater than because it's wrapped in a tarball with `random.Layer`
if len(payload) < 300 {
t.Errorf("Payload() = %d bytes, wanted %d", len(payload), 300)
}
}
40 changes: 39 additions & 1 deletion pkg/oci/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package remote

import (
"fmt"
"io"

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
Expand Down Expand Up @@ -154,5 +155,42 @@ func attachment(digestable digestable, attName string, o *options) (oci.File, er
if err != nil {
return nil, err
}
return oci.File(img), nil
ls, err := img.Layers()
if err != nil {
return nil, err
}
if len(ls) != 1 {
return nil, fmt.Errorf("expected exactly one layer in attachment, got %d", len(ls))
}

return &attache{
SignedImage: img,
layer: ls[0],
}, nil
}

type attache struct {
oci.SignedImage
layer v1.Layer
}

var _ oci.File = (*attache)(nil)

// FileMediaType implements oci.File
func (f *attache) FileMediaType() (types.MediaType, error) {
return f.layer.MediaType()
}

// Payload implements oci.File
func (f *attache) Payload() ([]byte, error) {
// remote layers are believed to be stored
// compressed, but we don't compress attachments
// so use "Compressed" to access the raw byte
// stream.
rc, err := f.layer.Compressed()
if err != nil {
return nil, err
}
defer rc.Close()
return io.ReadAll(rc)
}
39 changes: 34 additions & 5 deletions pkg/oci/static/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
package static

import (
"io"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/types"
Expand All @@ -31,14 +34,40 @@ func NewFile(payload []byte, opts ...Option) (oci.File, error) {
}
base := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
base = mutate.ConfigMediaType(base, o.ConfigMediaType)
layer := &staticLayer{
b: payload,
opts: o,
}
img, err := mutate.Append(base, mutate.Addendum{
Layer: &staticLayer{
b: payload,
opts: o,
},
Layer: layer,
})
if err != nil {
return nil, err
}
return signed.Image(img), nil
return &file{
SignedImage: signed.Image(img),
layer: layer,
}, nil
}

type file struct {
oci.SignedImage
layer v1.Layer
}

var _ oci.File = (*file)(nil)

// FileMediaType implements oci.File
func (f *file) FileMediaType() (types.MediaType, error) {
return f.layer.MediaType()
}

// Payload implements oci.File
func (f *file) Payload() ([]byte, error) {
rc, err := f.layer.Uncompressed()
if err != nil {
return nil, err
}
defer rc.Close()
return io.ReadAll(rc)
}
14 changes: 11 additions & 3 deletions pkg/oci/static/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import (

func TestNewFile(t *testing.T) {
payload := "this is the content!"
img, err := NewFile([]byte(payload), WithLayerMediaType("foo"))
file, err := NewFile([]byte(payload), WithLayerMediaType("foo"))
if err != nil {
t.Fatalf("NewFile() = %v", err)
}

layers, err := img.Layers()
layers, err := file.Layers()
if err != nil {
t.Fatalf("Layers() = %v", err)
} else if got, want := len(layers), 1; got != want {
Expand All @@ -53,7 +53,7 @@ func TestNewFile(t *testing.T) {

t.Run("check media type", func(t *testing.T) {
wantMT := types.MediaType("foo")
gotMT, err := l.MediaType()
gotMT, err := file.FileMediaType()
if err != nil {
t.Fatalf("MediaType() = %v", err)
}
Expand Down Expand Up @@ -111,5 +111,13 @@ func TestNewFile(t *testing.T) {
if got, want := string(uncompContent), payload; got != want {
t.Errorf("Uncompressed() = %s, wanted %s", got, want)
}

gotPayload, err := file.Payload()
if err != nil {
t.Fatalf("Payload() = %v", err)
}
if got, want := string(gotPayload), payload; got != want {
t.Errorf("Payload() = %s, wanted %s", got, want)
}
})
}
4 changes: 2 additions & 2 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ func TestAttachSBOM(t *testing.T) {
if err == nil {
t.Fatal("Expected error")
}
t.Log(out)
t.Log(out.String())
out.Reset()

// Upload it!
Expand All @@ -632,7 +632,7 @@ func TestAttachSBOM(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Log(out)
t.Log(out.String())
if len(sboms) != 1 {
t.Fatalf("Expected one sbom, got %d", len(sboms))
}
Expand Down