diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index ae07ac51adf..0260bdc5512 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -189,7 +189,7 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt return nil } - opts := []static.Option{static.WithMediaType(types.DssePayloadType)} + opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} if sv.Cert != nil { opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) } diff --git a/pkg/cosign/remote/index.go b/pkg/cosign/remote/index.go index 3224d9224ca..ec1c96b1270 100644 --- a/pkg/cosign/remote/index.go +++ b/pkg/cosign/remote/index.go @@ -138,20 +138,12 @@ func UploadFiles(ref name.Reference, files []File, getMt MediaTypeGetter, remote return ref.Context().Digest(lastHash.String()), nil } -func UploadFile(b []byte, ref name.Reference, layerMT, configMt types.MediaType, remoteOpts ...remote.Option) (v1.Image, error) { - l, err := static.NewFile(b, static.WithMediaType(layerMT)) +func UploadFile(b []byte, ref name.Reference, layerMT, configMT types.MediaType, remoteOpts ...remote.Option) (v1.Image, error) { + img, err := static.NewFile(b, static.WithLayerMediaType(layerMT), static.WithConfigMediaType(configMT)) if err != nil { return nil, err } - emptyOci := mutate.MediaType(empty.Image, types.OCIManifestSchema1) - img, err := mutate.Append(emptyOci, mutate.Addendum{ - Layer: l, - }) - if err != nil { - return nil, err - } - img = mutate.ConfigMediaType(img, configMt) if err := remote.Write(ref, img, remoteOpts...); err != nil { return nil, err } diff --git a/pkg/oci/file.go b/pkg/oci/file.go new file mode 100644 index 00000000000..5fc68cec16a --- /dev/null +++ b/pkg/oci/file.go @@ -0,0 +1,23 @@ +// +// 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 oci + +// 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. +} diff --git a/pkg/oci/static/file.go b/pkg/oci/static/file.go new file mode 100644 index 00000000000..7542af874db --- /dev/null +++ b/pkg/oci/static/file.go @@ -0,0 +1,44 @@ +// +// 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 static + +import ( + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/sigstore/cosign/pkg/oci" + "github.com/sigstore/cosign/pkg/oci/signed" +) + +// NewFile constructs a new v1.Image with the provided payload. +func NewFile(payload []byte, opts ...Option) (oci.File, error) { + o, err := makeOptions(opts...) + if err != nil { + return nil, err + } + base := mutate.MediaType(empty.Image, types.OCIManifestSchema1) + base = mutate.ConfigMediaType(base, o.ConfigMediaType) + img, err := mutate.Append(base, mutate.Addendum{ + Layer: &staticLayer{ + b: payload, + opts: o, + }, + }) + if err != nil { + return nil, err + } + return signed.Image(img), nil +} diff --git a/pkg/oci/static/file_test.go b/pkg/oci/static/file_test.go new file mode 100644 index 00000000000..4666ed06c08 --- /dev/null +++ b/pkg/oci/static/file_test.go @@ -0,0 +1,115 @@ +// +// 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 static + +import ( + "io" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +func TestNewFile(t *testing.T) { + payload := "this is the content!" + img, err := NewFile([]byte(payload), WithLayerMediaType("foo")) + if err != nil { + t.Fatalf("NewFile() = %v", err) + } + + layers, err := img.Layers() + if err != nil { + t.Fatalf("Layers() = %v", err) + } else if got, want := len(layers), 1; got != want { + t.Fatalf("len(Layers()) = %d, wanted %d", got, want) + } + l := layers[0] + + t.Run("check size", func(t *testing.T) { + wantSize := int64(len(payload)) + gotSize, err := l.Size() + if err != nil { + t.Fatalf("Size() = %v", err) + } + if gotSize != wantSize { + t.Errorf("Size() = %d, wanted %d", gotSize, wantSize) + } + }) + + t.Run("check media type", func(t *testing.T) { + wantMT := types.MediaType("foo") + gotMT, err := l.MediaType() + if err != nil { + t.Fatalf("MediaType() = %v", err) + } + if gotMT != wantMT { + t.Errorf("MediaType() = %s, wanted %s", gotMT, wantMT) + } + }) + + t.Run("check hashes", func(t *testing.T) { + wantHash, _, err := v1.SHA256(strings.NewReader(payload)) + if err != nil { + t.Fatalf("SHA256() = %v", err) + } + + gotDigest, err := l.Digest() + if err != nil { + t.Fatalf("Digest() = %v", err) + } + if !cmp.Equal(gotDigest, wantHash) { + t.Errorf("Digest = %s", cmp.Diff(gotDigest, wantHash)) + } + + gotDiffID, err := l.DiffID() + if err != nil { + t.Fatalf("DiffID() = %v", err) + } + if !cmp.Equal(gotDiffID, wantHash) { + t.Errorf("DiffID = %s", cmp.Diff(gotDiffID, wantHash)) + } + }) + + t.Run("check content", func(t *testing.T) { + comp, err := l.Compressed() + if err != nil { + t.Fatalf("Compressed() = %v", err) + } + defer comp.Close() + compContent, err := io.ReadAll(comp) + if err != nil { + t.Fatalf("ReadAll() = %v", err) + } + if got, want := string(compContent), payload; got != want { + t.Errorf("Compressed() = %s, wanted %s", got, want) + } + + uncomp, err := l.Uncompressed() + if err != nil { + t.Fatalf("Uncompressed() = %v", err) + } + defer uncomp.Close() + uncompContent, err := io.ReadAll(uncomp) + if err != nil { + t.Fatalf("ReadAll() = %v", err) + } + if got, want := string(uncompContent), payload; got != want { + t.Errorf("Uncompressed() = %s, wanted %s", got, want) + } + }) +} diff --git a/pkg/oci/static/options.go b/pkg/oci/static/options.go index 9d19aa40af5..9220c06b63e 100644 --- a/pkg/oci/static/options.go +++ b/pkg/oci/static/options.go @@ -26,17 +26,19 @@ import ( type Option func(*options) type options struct { - MediaType types.MediaType - Bundle *oci.Bundle - Cert []byte - Chain []byte - Annotations map[string]string + LayerMediaType types.MediaType + ConfigMediaType types.MediaType + Bundle *oci.Bundle + Cert []byte + Chain []byte + Annotations map[string]string } func makeOptions(opts ...Option) (*options, error) { o := &options{ - MediaType: ctypes.SimpleSigningMediaType, - Annotations: make(map[string]string), + LayerMediaType: ctypes.SimpleSigningMediaType, + ConfigMediaType: types.OCIConfigJSON, + Annotations: make(map[string]string), } for _, opt := range opts { @@ -59,10 +61,17 @@ func makeOptions(opts ...Option) (*options, error) { return o, nil } -// WithMediaType sets the media type of the signature. -func WithMediaType(mt types.MediaType) Option { +// WithLayerMediaType sets the media type of the signature. +func WithLayerMediaType(mt types.MediaType) Option { return func(o *options) { - o.MediaType = mt + o.LayerMediaType = mt + } +} + +// WithConfigMediaType sets the media type of the signature. +func WithConfigMediaType(mt types.MediaType) Option { + return func(o *options) { + o.ConfigMediaType = mt } } diff --git a/pkg/oci/static/options_test.go b/pkg/oci/static/options_test.go index 2b4c835d674..5994c980c09 100644 --- a/pkg/oci/static/options_test.go +++ b/pkg/oci/static/options_test.go @@ -19,6 +19,7 @@ import ( "reflect" "testing" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/pkg/oci" ctypes "github.com/sigstore/cosign/pkg/types" ) @@ -33,15 +34,25 @@ func TestOptions(t *testing.T) { }{{ name: "no options", want: &options{ - MediaType: ctypes.SimpleSigningMediaType, - Annotations: make(map[string]string), + LayerMediaType: ctypes.SimpleSigningMediaType, + ConfigMediaType: types.OCIConfigJSON, + Annotations: make(map[string]string), }, }, { - name: "with media type", - opts: []Option{WithMediaType("foo")}, + name: "with layer media type", + opts: []Option{WithLayerMediaType("foo")}, want: &options{ - MediaType: "foo", - Annotations: make(map[string]string), + LayerMediaType: "foo", + ConfigMediaType: types.OCIConfigJSON, + Annotations: make(map[string]string), + }, + }, { + name: "with config media type", + opts: []Option{WithConfigMediaType("bar")}, + want: &options{ + LayerMediaType: ctypes.SimpleSigningMediaType, + ConfigMediaType: "bar", + Annotations: make(map[string]string), }, }, { name: "with annotations", @@ -49,7 +60,8 @@ func TestOptions(t *testing.T) { "foo": "bar", })}, want: &options{ - MediaType: ctypes.SimpleSigningMediaType, + LayerMediaType: ctypes.SimpleSigningMediaType, + ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ "foo": "bar", }, @@ -58,7 +70,8 @@ func TestOptions(t *testing.T) { name: "with cert chain", opts: []Option{WithCertChain([]byte("a"), []byte("b"))}, want: &options{ - MediaType: ctypes.SimpleSigningMediaType, + LayerMediaType: ctypes.SimpleSigningMediaType, + ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ CertificateAnnotationKey: "a", ChainAnnotationKey: "b", @@ -70,7 +83,8 @@ func TestOptions(t *testing.T) { name: "with bundle", opts: []Option{WithBundle(bundle)}, want: &options{ - MediaType: ctypes.SimpleSigningMediaType, + LayerMediaType: ctypes.SimpleSigningMediaType, + ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ BundleAnnotationKey: "{\"SignedEntryTimestamp\":\"\",\"Payload\":{\"body\":null,\"integratedTime\":0,\"logIndex\":0,\"logID\":\"\"}}", }, diff --git a/pkg/oci/static/signature.go b/pkg/oci/static/signature.go index 55d578f1865..1503f82eafb 100644 --- a/pkg/oci/static/signature.go +++ b/pkg/oci/static/signature.go @@ -53,11 +53,6 @@ func NewAttestation(payload []byte, opts ...Option) (oci.Signature, error) { return NewSignature(payload, "", opts...) } -// NewFile constructs a new v1.Layer with the provided payload. -func NewFile(payload []byte, opts ...Option) (v1.Layer, error) { - return NewSignature(payload, "", opts...) -} - type staticLayer struct { b []byte b64sig string @@ -139,5 +134,5 @@ func (l *staticLayer) Size() (int64, error) { // MediaType implements v1.Layer func (l *staticLayer) MediaType() (types.MediaType, error) { - return l.opts.MediaType, nil + return l.opts.LayerMediaType, nil } diff --git a/pkg/oci/static/signature_test.go b/pkg/oci/static/signature_test.go index 704d2278540..e6b03197977 100644 --- a/pkg/oci/static/signature_test.go +++ b/pkg/oci/static/signature_test.go @@ -27,91 +27,10 @@ import ( "github.com/sigstore/cosign/pkg/oci" ) -func TestNewFile(t *testing.T) { - payload := "this is the content!" - l, err := NewFile([]byte(payload), WithMediaType("foo")) - if err != nil { - t.Fatalf("NewFile() = %v", err) - } - - t.Run("check size", func(t *testing.T) { - wantSize := int64(len(payload)) - gotSize, err := l.Size() - if err != nil { - t.Fatalf("Size() = %v", err) - } - if gotSize != wantSize { - t.Errorf("Size() = %d, wanted %d", gotSize, wantSize) - } - }) - - t.Run("check media type", func(t *testing.T) { - wantMT := types.MediaType("foo") - gotMT, err := l.MediaType() - if err != nil { - t.Fatalf("MediaType() = %v", err) - } - if gotMT != wantMT { - t.Errorf("MediaType() = %s, wanted %s", gotMT, wantMT) - } - }) - - t.Run("check hashes", func(t *testing.T) { - wantHash, _, err := v1.SHA256(strings.NewReader(payload)) - if err != nil { - t.Fatalf("SHA256() = %v", err) - } - - gotDigest, err := l.Digest() - if err != nil { - t.Fatalf("Digest() = %v", err) - } - if !cmp.Equal(gotDigest, wantHash) { - t.Errorf("Digest = %s", cmp.Diff(gotDigest, wantHash)) - } - - gotDiffID, err := l.DiffID() - if err != nil { - t.Fatalf("DiffID() = %v", err) - } - if !cmp.Equal(gotDiffID, wantHash) { - t.Errorf("DiffID = %s", cmp.Diff(gotDiffID, wantHash)) - } - }) - - t.Run("check content", func(t *testing.T) { - comp, err := l.Compressed() - if err != nil { - t.Fatalf("Compressed() = %v", err) - } - defer comp.Close() - compContent, err := io.ReadAll(comp) - if err != nil { - t.Fatalf("ReadAll() = %v", err) - } - if got, want := string(compContent), payload; got != want { - t.Errorf("Compressed() = %s, wanted %s", got, want) - } - - uncomp, err := l.Uncompressed() - if err != nil { - t.Fatalf("Uncompressed() = %v", err) - } - defer uncomp.Close() - uncompContent, err := io.ReadAll(uncomp) - if err != nil { - t.Fatalf("ReadAll() = %v", err) - } - if got, want := string(uncompContent), payload; got != want { - t.Errorf("Uncompressed() = %s, wanted %s", got, want) - } - }) -} - func TestNewSignatureBasic(t *testing.T) { payload := "this is the content!" b64sig := "b64 content==" - l, err := NewSignature([]byte(payload), b64sig, WithMediaType("foo")) + l, err := NewSignature([]byte(payload), b64sig, WithLayerMediaType("foo")) if err != nil { t.Fatalf("NewSignature() = %v", err) } @@ -265,7 +184,7 @@ func TestNewSignatureBasic(t *testing.T) { func TestNewAttestationBasic(t *testing.T) { payload := "this is the content!" - l, err := NewAttestation([]byte(payload), WithMediaType("foo")) + l, err := NewAttestation([]byte(payload), WithLayerMediaType("foo")) if err != nil { t.Fatalf("NewSignature() = %v", err) }