diff --git a/pkg/oci/layout/image.go b/pkg/oci/layout/image.go deleted file mode 100644 index dff0394ef70..00000000000 --- a/pkg/oci/layout/image.go +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright 2021 The Sigstore Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package layout - -import ( - "fmt" - - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/layout" - "github.com/sigstore/cosign/pkg/oci" - "github.com/sigstore/cosign/pkg/oci/internal/signature" -) - -// SignedImage provides access to a remote image reference, and its signatures. -func SignedImage(path string) (oci.SignedImage, error) { - p, err := layout.FromPath(imagePath(path)) - if err != nil { - return nil, err - } - // there should only be one image in the index, so we can pass in empty hash to get it - img, err := p.Image(v1.Hash{}) - if err != nil { - return nil, err - } - - return &image{ - Image: img, - path: path, - }, nil -} - -type image struct { - path string - v1.Image -} - -var _ oci.SignedImage = (*image)(nil) - -type sigs struct { - v1.Image -} - -var _ oci.Signatures = (*sigs)(nil) - -// Get implements oci.Signatures -func (s *sigs) Get() ([]oci.Signature, error) { - manifest, err := s.Image.Manifest() - if err != nil { - return nil, err - } - signatures := make([]oci.Signature, 0, len(manifest.Layers)) - for _, desc := range manifest.Layers { - l, err := s.Image.LayerByDigest(desc.Digest) - if err != nil { - return nil, err - } - signatures = append(signatures, signature.New(l, desc)) - } - return signatures, nil -} - -// Signatures implements oci.SignedImage -func (i *image) Signatures() (oci.Signatures, error) { - sigPath, err := layout.FromPath(signaturesPath(i.path)) - if err != nil { - return nil, err - } - // there should only be one image in the index, so we can pass in empty hash to get it - img, err := sigPath.Image(v1.Hash{}) - if err != nil { - return nil, err - } - return &sigs{ - Image: img, - }, nil -} - -// Attestations implements oci.SignedImage -// TODO (priyawadhwa@) -func (i *image) Attestations() (oci.Signatures, error) { - return nil, fmt.Errorf("not yet implemented") -} - -// Attestations implements oci.SignedImage -// TODO (priyawadhwa@) -func (i *image) Attachment(name string) (oci.File, error) { - return nil, fmt.Errorf("not yet implemented") -} diff --git a/pkg/oci/layout/index.go b/pkg/oci/layout/index.go new file mode 100644 index 00000000000..55714b6f1c9 --- /dev/null +++ b/pkg/oci/layout/index.go @@ -0,0 +1,113 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package layout + +import ( + "fmt" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/sigstore/cosign/pkg/oci" + "github.com/sigstore/cosign/pkg/oci/signed" +) + +const ( + imageAnnotation = "dev.cosignproject.cosign/image" + sigsAnnotation = "dev.cosignproject.cosign/sigs" +) + +// SignedImageIndex provides access to a local index reference, and its signatures. +func SignedImageIndex(path string) (oci.SignedImageIndex, error) { + p, err := layout.FromPath(path) + if err != nil { + return nil, err + } + ii, err := p.ImageIndex() + if err != nil { + return nil, err + } + return &index{ + v1Index: ii, + }, nil +} + +// We alias ImageIndex so that we can inline it without the type +// name colliding with the name of a method it had to implement. +type v1Index v1.ImageIndex + +type index struct { + v1Index +} + +var _ oci.SignedImageIndex = (*index)(nil) + +// Signatures implements oci.SignedImageIndex +func (i *index) Signatures() (oci.Signatures, error) { + sigsImage, err := i.imageByAnnotation(sigsAnnotation) + if err != nil { + return nil, err + } + return &sigs{sigsImage}, nil +} + +// Attestations implements oci.SignedImageIndex +func (i *index) Attestations() (oci.Signatures, error) { + return nil, fmt.Errorf("not yet implemented") +} + +// Attestations implements oci.SignedImage +func (i *index) Attachment(name string) (oci.File, error) { + return nil, fmt.Errorf("not yet implemented") +} + +// SignedImage implements oci.SignedImageIndex +func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) { + img, err := i.Image(h) + if err != nil { + return nil, err + } + return signed.Image(img), nil +} + +// 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) { + manifest, err := i.IndexManifest() + if err != nil { + return nil, err + } + for _, m := range manifest.Manifests { + if _, ok := m.Annotations[annotation]; ok { + img, err := i.Image(m.Digest) + if err != nil { + return nil, err + } + return img, nil + } + } + return nil, fmt.Errorf("unable to find image") +} + +// SignedImageIndex implements oci.SignedImageIndex +func (i *index) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) { + ii, err := i.ImageIndex(h) + if err != nil { + return nil, err + } + return &index{ + v1Index: ii, + }, nil +} diff --git a/pkg/oci/layout/signatures.go b/pkg/oci/layout/signatures.go new file mode 100644 index 00000000000..c95388082b5 --- /dev/null +++ b/pkg/oci/layout/signatures.go @@ -0,0 +1,45 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package layout + +import ( + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/sigstore/cosign/pkg/oci" + "github.com/sigstore/cosign/pkg/oci/internal/signature" +) + +type sigs struct { + v1.Image +} + +var _ oci.Signatures = (*sigs)(nil) + +// Get implements oci.Signatures +func (s *sigs) Get() ([]oci.Signature, error) { + manifest, err := s.Image.Manifest() + if err != nil { + return nil, err + } + signatures := make([]oci.Signature, 0, len(manifest.Layers)) + for _, desc := range manifest.Layers { + l, err := s.Image.LayerByDigest(desc.Digest) + if err != nil { + return nil, err + } + signatures = append(signatures, signature.New(l, desc)) + } + return signatures, nil +} diff --git a/pkg/oci/layout/write.go b/pkg/oci/layout/write.go index 932ff2c43d5..c9d56308cc6 100644 --- a/pkg/oci/layout/write.go +++ b/pkg/oci/layout/write.go @@ -16,8 +16,6 @@ package layout import ( - "path/filepath" - v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/layout" @@ -27,38 +25,29 @@ import ( // WriteSignedImage writes the image and all related signatures, attestations and attachments func WriteSignedImage(path string, si oci.SignedImage) error { - // First, write the image - if err := write(path, imagePath, si); err != nil { - return errors.Wrap(err, "writing image") + // First, write an empty index + layoutPath, err := layout.Write(path, empty.Index) + if err != nil { + return err } + // write the image + if err := appendImage(layoutPath, si, imageAnnotation); err != nil { + return errors.Wrap(err, "appending signed image") + } + // write the signatures sigs, err := si.Signatures() if err != nil { return errors.Wrap(err, "getting signatures") } - if err := write(path, signaturesPath, sigs); err != nil { - return errors.Wrap(err, "writing signatures") + if err := appendImage(layoutPath, sigs, sigsAnnotation); err != nil { + return errors.Wrap(err, "appending signatures") } // TODO (priyawadhwa@) write attestations and attachments return nil } -func imagePath(path string) string { - return filepath.Join(path, "image") -} - -func signaturesPath(path string) string { - return filepath.Join(path, "sigs") -} - -type pathFunc func(string) string - -func write(path string, pf pathFunc, img v1.Image) error { - p := pf(path) - // write empty image - layoutPath, err := layout.Write(p, empty.Index) - if err != nil { - return err - } - // write image to disk - return layoutPath.AppendImage(img) +func appendImage(path layout.Path, img v1.Image, annotation string) error { + return path.AppendImage(img, layout.WithAnnotations( + map[string]string{annotation: "true"}, + )) } diff --git a/pkg/oci/layout/write_test.go b/pkg/oci/layout/write_test.go index fa47b518aa0..e3f4ae934c1 100644 --- a/pkg/oci/layout/write_test.go +++ b/pkg/oci/layout/write_test.go @@ -51,11 +51,11 @@ func TestReadWrite(t *testing.T) { t.Fatal(err) } // read the image and make sure the signatures exist - image, err := SignedImage(tmp) + imageIndex, err := SignedImageIndex(tmp) if err != nil { t.Fatal(err) } - sigImage, err := image.Signatures() + sigImage, err := imageIndex.Signatures() if err != nil { t.Fatal(err) }