From ace07e7c5aaa46de09106a6504df3dc6e10118f4 Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Wed, 15 Sep 2021 20:17:57 -0700 Subject: [PATCH] Create our very own `empty` package. Cosign has it's own flavor of GGCR's `empty` package, which lets users toggle the media type we use with an environment variable. This creates our own package to mirror this. This also start to set up an `internal/` package structure that we can use to develop the new package structure without (yet) worrying about breaking folks downstream when we change things. Related: https://github.com/sigstore/cosign/issues/666 Signed-off-by: Matt Moore --- internal/oci/doc.go | 20 +++++++++ internal/oci/empty/empty.go | 39 +++++++++++++++++ internal/oci/empty/empty_test.go | 72 ++++++++++++++++++++++++++++++++ internal/oci/oci.go | 32 ++++++++++++++ pkg/cosign/remote/remote.go | 48 +++++++-------------- 5 files changed, 177 insertions(+), 34 deletions(-) create mode 100644 internal/oci/doc.go create mode 100644 internal/oci/empty/empty.go create mode 100644 internal/oci/empty/empty_test.go create mode 100644 internal/oci/oci.go diff --git a/internal/oci/doc.go b/internal/oci/doc.go new file mode 100644 index 00000000000..5ddb6e10339 --- /dev/null +++ b/internal/oci/doc.go @@ -0,0 +1,20 @@ +// +// 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 holds functions and types intended to align and compose with +// github.com/google/go-containerregistry. +// This is under internal/ to avoid folks taking a dependency on these while +// they are still in flux. +package oci diff --git a/internal/oci/empty/empty.go b/internal/oci/empty/empty.go new file mode 100644 index 00000000000..cb25e9c7fd4 --- /dev/null +++ b/internal/oci/empty/empty.go @@ -0,0 +1,39 @@ +// +// 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 empty + +import ( + 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" + "github.com/sigstore/cosign/internal/oci" +) + +// Image constructs an empty image on which to base signature images. +func Image() v1.Image { + base := empty.Image + if !oci.DockerMediaTypes() { + base = mutate.MediaType(base, types.OCIManifestSchema1) + m, err := base.Manifest() + if err != nil { + // It is impossible for this to happen. + panic(err.Error()) + } + m.Config.MediaType = types.OCIConfigJSON + } + return base +} diff --git a/internal/oci/empty/empty_test.go b/internal/oci/empty/empty_test.go new file mode 100644 index 00000000000..5ae2ce63abb --- /dev/null +++ b/internal/oci/empty/empty_test.go @@ -0,0 +1,72 @@ +// +// 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 empty + +import ( + "os" + "testing" + + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/sigstore/cosign/internal/oci" +) + +func TestEmptyImage(t *testing.T) { + tests := []struct { + name string + value string + wantMT types.MediaType + wantConfigMT types.MediaType + }{{ + name: "unset", + wantMT: types.OCIManifestSchema1, + wantConfigMT: types.OCIConfigJSON, + }, { + name: "set false", + value: "false", + wantMT: types.OCIManifestSchema1, + wantConfigMT: types.OCIConfigJSON, + }, { + name: "set true", + value: "true", + wantMT: types.DockerManifestSchema2, + wantConfigMT: types.DockerConfigJSON, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := os.Setenv(oci.DockerMediaTypesEnv, test.value); err != nil { + t.Fatalf("Setenv() = %v", err) + } + + img := Image() + + if mt, err := img.MediaType(); err != nil { + t.Errorf("MediaType() = %v", err) + } else if mt != test.wantMT { + t.Errorf("MediaType() = %v, wanted %v", mt, test.wantMT) + } + + m, err := img.Manifest() + if err != nil { + t.Fatalf("ConfigFile() = %v", err) + } + + if mt := m.Config.MediaType; mt != test.wantConfigMT { + t.Errorf("Config.MediaType = %v, wanted %v", mt, test.wantConfigMT) + } + }) + } +} diff --git a/internal/oci/oci.go b/internal/oci/oci.go new file mode 100644 index 00000000000..d228a82ceec --- /dev/null +++ b/internal/oci/oci.go @@ -0,0 +1,32 @@ +// +// 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 + +import ( + "os" + "strconv" +) + +const ( + DockerMediaTypesEnv = "COSIGN_DOCKER_MEDIA_TYPES" +) + +func DockerMediaTypes() bool { + if b, err := strconv.ParseBool(os.Getenv(DockerMediaTypesEnv)); err == nil { + return b + } + return false +} diff --git a/pkg/cosign/remote/remote.go b/pkg/cosign/remote/remote.go index e50ee5b3566..04192b9275b 100644 --- a/pkg/cosign/remote/remote.go +++ b/pkg/cosign/remote/remote.go @@ -21,29 +21,26 @@ import ( "io" "io/ioutil" "net/http" - "os" - "strconv" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" "github.com/google/go-containerregistry/pkg/name" 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/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/pkg/errors" + "github.com/sigstore/cosign/internal/oci/empty" ctypes "github.com/sigstore/cosign/pkg/types" "github.com/sigstore/sigstore/pkg/signature" ) const ( - sigkey = "dev.cosignproject.cosign/signature" - certkey = "dev.sigstore.cosign/certificate" - chainkey = "dev.sigstore.cosign/chain" - BundleKey = "dev.sigstore.cosign/bundle" - DockerMediaTypesEnv = "COSIGN_DOCKER_MEDIA_TYPES" + sigkey = "dev.cosignproject.cosign/signature" + certkey = "dev.sigstore.cosign/certificate" + chainkey = "dev.sigstore.cosign/chain" + BundleKey = "dev.sigstore.cosign/bundle" ) func Descriptors(ref name.Reference, remoteOpts ...remote.Option) ([]v1.Descriptor, error) { @@ -59,37 +56,20 @@ func Descriptors(ref name.Reference, remoteOpts ...remote.Option) ([]v1.Descript return m.Layers, nil } -func DockerMediaTypes() bool { - if b, err := strconv.ParseBool(os.Getenv(DockerMediaTypesEnv)); err == nil { - return b - } - return false -} - // SignatureImage returns the existing destination image, or a new, empty one. func SignatureImage(ref name.Reference, opts ...remote.Option) (v1.Image, error) { base, err := remote.Image(ref, opts...) - if err != nil { - var te *transport.Error - if errors.As(err, &te) { - if te.StatusCode != http.StatusNotFound { - return nil, te - } - base = empty.Image - if !DockerMediaTypes() { - base = mutate.MediaType(base, types.OCIManifestSchema1) - m, err := base.Manifest() - if err != nil { - // should never happen...? - return nil, err - } - m.Config.MediaType = types.OCIConfigJSON - } - } else { - return nil, err + if err == nil { + return base, nil + } + var te *transport.Error + if errors.As(err, &te) { + if te.StatusCode != http.StatusNotFound { + return nil, te } + return empty.Image(), nil } - return base, nil + return nil, err } func findDuplicate(sigImage v1.Image, payload []byte, dupeDetector signature.Verifier, annotations map[string]string) ([]byte, error) {