Skip to content

Commit

Permalink
Add COSIGN_OCI_EXPERIMENTAL, push .sig/.sbom using OCI 1.1+ digest tag (
Browse files Browse the repository at this point in the history
sigstore#2684)

* Add COSIGN_OCI_EXPERIMENTAL, push .sig/.sbom using OCI 1.1+ digest tag

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* upgrade to latest ggcr with referrers API support

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* unexport methods whereever possible

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* Use ui logger wherever possible

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* add TODO about using created annotations

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* remove logging about unable to to find new type of attachment

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* fixes from review

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* push the config blob

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* run docgen

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

* fix for e2e tests

Signed-off-by: Josh Dolitsky <josh@dolit.ski>

---------

Signed-off-by: Josh Dolitsky <josh@dolit.ski>
  • Loading branch information
jdolitsky authored and dmitris committed Mar 24, 2023
1 parent 1426f1b commit 347a658
Show file tree
Hide file tree
Showing 16 changed files with 410 additions and 17 deletions.
2 changes: 1 addition & 1 deletion cmd/cosign/cli/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func attachSBOM() *cobra.Command {
return err
}
fmt.Fprintf(os.Stderr, "WARNING: Attaching SBOMs this way does not sign them. If you want to sign them, use 'cosign attest --predicate %s --key <key path>' or 'cosign sign --key <key path> --attachment sbom <image uri>'.\n", o.SBOM)
return attach.SBOMCmd(cmd.Context(), o.Registry, o.SBOM, mediaType, args[0])
return attach.SBOMCmd(cmd.Context(), o.Registry, o.RegistryExperimental, o.SBOM, mediaType, args[0])
},
}

Expand Down
80 changes: 77 additions & 3 deletions cmd/cosign/cli/attach/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,31 @@ import (
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"

"github.com/google/go-containerregistry/pkg/logs"
"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/types"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
ocistatic "github.com/google/go-containerregistry/pkg/v1/static"
ocitypes "github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote"
"github.com/sigstore/cosign/v2/internal/ui"
ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
"github.com/sigstore/cosign/v2/pkg/oci/static"
)

func SBOMCmd(ctx context.Context, regOpts options.RegistryOptions, sbomRef string, sbomType types.MediaType, imageRef string) error {
func SBOMCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts options.RegistryExperimentalOptions, sbomRef string, sbomType ocitypes.MediaType, imageRef string) error {
if regExpOpts.RegistryReferrersMode == options.RegistryReferrersModeOCI11 {
return sbomCmdOCIExperimental(ctx, regOpts, sbomRef, sbomType, imageRef)
}

ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
if err != nil {
return err
Expand All @@ -52,14 +65,75 @@ func SBOMCmd(ctx context.Context, regOpts options.RegistryOptions, sbomRef strin
return err
}

fmt.Fprintf(os.Stderr, "Uploading SBOM file for [%s] to [%s] with mediaType [%s].\n", ref.Name(), dstRef.Name(), sbomType)
ui.Infof(ctx, "Uploading SBOM file for [%s] to [%s] with mediaType [%s].\n", ref.Name(), dstRef.Name(), sbomType)
img, err := static.NewFile(b, static.WithLayerMediaType(sbomType))
if err != nil {
return err
}
return remote.Write(dstRef, img, regOpts.GetRegistryClientOpts(ctx)...)
}

func sbomCmdOCIExperimental(ctx context.Context, regOpts options.RegistryOptions, sbomRef string, sbomType ocitypes.MediaType, imageRef string) error {
var dig name.Digest
ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
if err != nil {
return err
}
if digr, ok := ref.(name.Digest); ok {
dig = digr
} else {
desc, err := remote.Head(ref, regOpts.GetRegistryClientOpts(ctx)...)
if err != nil {
return err
}
dig = ref.Context().Digest(desc.Digest.String())
}

artifactType := ociexperimental.ArtifactType("sbom")

desc, err := remote.Head(dig, regOpts.GetRegistryClientOpts(ctx)...)
var terr *transport.Error
if errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound {
h, err := v1.NewHash(dig.DigestStr())
if err != nil {
return err
}
// The subject doesn't exist, attach to it as if it's an empty OCI image.
logs.Progress.Println("subject doesn't exist, attaching to empty image")
desc = &v1.Descriptor{
ArtifactType: artifactType,
MediaType: ocitypes.OCIManifestSchema1,
Size: 0,
Digest: h,
}
} else if err != nil {
return err
}

b, err := sbomBytes(sbomRef)
if err != nil {
return err
}

empty := mutate.MediaType(
mutate.ConfigMediaType(empty.Image, ocitypes.MediaType(artifactType)),
ocitypes.OCIManifestSchema1)
att, err := mutate.AppendLayers(empty, ocistatic.NewLayer(b, sbomType))
if err != nil {
return err
}
att = mutate.Subject(att, *desc).(v1.Image)
attdig, err := att.Digest()
if err != nil {
return err
}
dstRef := ref.Context().Digest(attdig.String())

fmt.Fprintf(os.Stderr, "Uploading SBOM file for [%s] to [%s] with config.mediaType [%s] layers[0].mediaType [%s].\n",
ref.Name(), dstRef.String(), artifactType, sbomType)
return remote.Write(dstRef, att, regOpts.GetRegistryClientOpts(ctx)...)
}

func sbomBytes(sbomRef string) ([]byte, error) {
// sbomRef can be "-", a string or a file.
switch signatureType(sbomRef) {
Expand Down
10 changes: 6 additions & 4 deletions cmd/cosign/cli/options/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@ func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) {

// AttachSBOMOptions is the top level wrapper for the attach sbom command.
type AttachSBOMOptions struct {
SBOM string
SBOMType string
SBOMInputFormat string
Registry RegistryOptions
SBOM string
SBOMType string
SBOMInputFormat string
Registry RegistryOptions
RegistryExperimental RegistryExperimentalOptions
}

var _ Interface = (*AttachSBOMOptions)(nil)

// AddFlags implements Interface
func (o *AttachSBOMOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
o.RegistryExperimental.AddFlags(cmd)

cmd.Flags().StringVar(&o.SBOM, "sbom", "",
"path to the sbom, or {-} for stdin")
Expand Down
46 changes: 46 additions & 0 deletions cmd/cosign/cli/options/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package options
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"

Expand Down Expand Up @@ -111,3 +113,47 @@ func (o *RegistryOptions) GetRegistryClientOpts(ctx context.Context) []remote.Op
}
return opts
}

type RegistryReferrersMode string

const (
RegistryReferrersModeLegacy RegistryReferrersMode = "legacy"
RegistryReferrersModeOCI11 RegistryReferrersMode = "oci-1-1"
)

func (e *RegistryReferrersMode) String() string {
return string(*e)
}

func (e *RegistryReferrersMode) Set(v string) error {
switch v {
case "legacy":
*e = RegistryReferrersMode(v)
return nil
case "oci-1-1":
if !EnableExperimental() {
return fmt.Errorf(`in order to use mode "%s", you must set COSIGN_EXPERIMENTAL=1`, v)
}
*e = RegistryReferrersMode(v)
return nil
default:
return errors.New(`must be one of "legacy", "oci-1-1"`)
}
}

func (e *RegistryReferrersMode) Type() string {
return "registryReferrersMode"
}

// RegistryExperimentalOptions is the wrapper for the registry experimental options.
type RegistryExperimentalOptions struct {
RegistryReferrersMode RegistryReferrersMode
}

var _ Interface = (*RegistryExperimentalOptions)(nil)

// AddFlags implements Interface
func (o *RegistryExperimentalOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().Var(&o.RegistryReferrersMode, "registry-referrers-mode",
"mode for fetching references from the registry. allowed: legacy, oci-1-1")
}
4 changes: 3 additions & 1 deletion cmd/cosign/cli/options/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ type SignOptions struct {
OIDC OIDCOptions
SecurityKey SecurityKeyOptions
AnnotationOptions
Registry RegistryOptions
Registry RegistryOptions
RegistryExperimental RegistryExperimentalOptions
}

var _ Interface = (*SignOptions)(nil)
Expand All @@ -54,6 +55,7 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.AnnotationOptions.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.RegistryExperimental.AddFlags(cmd)

cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")
Expand Down
12 changes: 9 additions & 3 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO
ErrDone = mutate.ErrSkipChildren
}
regOpts := signOpts.Registry
regExpOpts := signOpts.RegistryExperimental
opts, err := regOpts.ClientOpts(ctx)
if err != nil {
return fmt.Errorf("constructing client options: %w", err)
Expand All @@ -182,7 +183,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO
if err != nil {
return fmt.Errorf("accessing image: %w", err)
}
err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, signOpts.Upload, signOpts.OutputSignature, signOpts.OutputCertificate, signOpts.Recursive, signOpts.TlogUpload, dd, sv, se)
err = signDigest(ctx, digest, staticPayload, ko, regOpts, regExpOpts, annotations, signOpts.Upload, signOpts.OutputSignature, signOpts.OutputCertificate, signOpts.Recursive, signOpts.TlogUpload, dd, sv, se)
if err != nil {
return fmt.Errorf("signing digest: %w", err)
}
Expand All @@ -201,7 +202,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO
return fmt.Errorf("computing digest: %w", err)
}
digest := ref.Context().Digest(d.String())
err = signDigest(ctx, digest, staticPayload, ko, regOpts, annotations, signOpts.Upload, signOpts.OutputSignature, signOpts.OutputCertificate, signOpts.Recursive, signOpts.TlogUpload, dd, sv, se)
err = signDigest(ctx, digest, staticPayload, ko, regOpts, regExpOpts, annotations, signOpts.Upload, signOpts.OutputSignature, signOpts.OutputCertificate, signOpts.Recursive, signOpts.TlogUpload, dd, sv, se)
if err != nil {
return fmt.Errorf("signing digest: %w", err)
}
Expand All @@ -215,7 +216,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO
}

func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko options.KeyOpts,
regOpts options.RegistryOptions, annotations map[string]interface{}, upload bool, outputSignature, outputCertificate string, recursive bool, tlogUpload bool,
regOpts options.RegistryOptions, regExpOpts options.RegistryExperimentalOptions, annotations map[string]interface{}, upload bool, outputSignature, outputCertificate string, recursive bool, tlogUpload bool,
dd mutate.DupeDetector, sv *SignerVerifier, se oci.SignedEntity) error {
var err error
// The payload can be passed to skip generation.
Expand Down Expand Up @@ -312,6 +313,11 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti
ui.Infof(ctx, "Pushing signature to: %s", repo.RepositoryStr())
}

// Publish the signatures associated with this entity (using OCI 1.1+ behavior)
if regExpOpts.RegistryReferrersMode == options.RegistryReferrersModeOCI11 {
return ociremote.WriteSignaturesExperimentalOCI(digest, newSE, walkOpts...)
}

// Publish the signatures associated with this entity
if err := ociremote.WriteSignatures(digest.Repository, newSE, walkOpts...); err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions doc/cosign_attach_sbom.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_sign.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/go-piv/piv-go v1.10.0
github.com/google/certificate-transparency-go v1.1.4
github.com/google/go-cmp v0.5.9
github.com/google/go-containerregistry v0.13.0
github.com/google/go-containerregistry v0.13.1-0.20230203223142-b3c23b4c3f28
github.com/google/go-github/v50 v50.0.0
github.com/in-toto/in-toto-golang v0.6.0
github.com/kelseyhightower/envconfig v1.4.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,8 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k=
github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo=
github.com/google/go-containerregistry v0.13.1-0.20230203223142-b3c23b4c3f28 h1:gFDKHwyCxpzgUozSOM8eLCx0V7muSr30QYU2QH+p48E=
github.com/google/go-containerregistry v0.13.1-0.20230203223142-b3c23b4c3f28/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo=
github.com/google/go-github/v50 v50.0.0 h1:gdO1AeuSZZK4iYWwVbjni7zg8PIQhp7QfmPunr016Jk=
github.com/google/go-github/v50 v50.0.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
Expand Down
25 changes: 25 additions & 0 deletions internal/pkg/oci/remote/remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Copyright 2023 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 remote

import (
"fmt"
)

// ArtifactType converts a attachment name (sig/sbom/att/etc.) into a valid artifactType (OCI 1.1+).
func ArtifactType(attName string) string {
return fmt.Sprintf("application/vnd.dev.cosign.artifact.%s.v1+json", attName)
}
Loading

0 comments on commit 347a658

Please sign in to comment.