Skip to content

Commit

Permalink
SBOM support in "cosign attach" and "cosign download sbom". (#387)
Browse files Browse the repository at this point in the history
This is ready!

Signed-off-by: Dan Lorenc <dlorenc@google.com>
  • Loading branch information
dlorenc authored Jun 26, 2021
1 parent 18c7aa9 commit d54af74
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 5 deletions.
2 changes: 1 addition & 1 deletion cmd/cosign/cli/attach/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func Attach() *ffcli.Command {
ShortUsage: "cosign attach",
ShortHelp: "attach contains tools to attach artifacts to other artifacts in a registry",
FlagSet: flagset,
Subcommands: []*ffcli.Command{Signature()},
Subcommands: []*ffcli.Command{Signature(), SBOM()},
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
Expand Down
112 changes: 112 additions & 0 deletions cmd/cosign/cli/attach/sbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// 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 attach

import (
"context"
"flag"
"io/ioutil"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"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/peterbourgon/ff/v3/ffcli"

"github.com/sigstore/cosign/pkg/cosign"
cremote "github.com/sigstore/cosign/pkg/cosign/remote"
)

var mediaTypes = map[string]string{
"cyclonedx": "application/vnd.cyclonedx",
"spdx": "text/spdx",
}

func SBOM() *ffcli.Command {
var (
flagset = flag.NewFlagSet("cosign attach sbom", flag.ExitOnError)
sbom = flagset.String("sbom", "", "path to the sbom, or {-} for stdin")
sbomType = flagset.String("type", "spdx", "type of sbom (spdx|cyclonedx), default spdx")
)
return &ffcli.Command{
Name: "sbom",
ShortUsage: "cosign attach sbom <image uri>",
ShortHelp: "attach sbom to the supplied container image",
FlagSet: flagset,
Exec: func(ctx context.Context, args []string) error {
if len(args) != 1 {
return flag.ErrHelp
}

mt, ok := mediaTypes[*sbomType]
if !ok {
return flag.ErrHelp
}

return SBOMCmd(ctx, *sbom, mt, args[0])
},
}
}

func SBOMCmd(ctx context.Context, sbomRef, sbomType, imageRef string) error {

ref, err := name.ParseReference(imageRef)
if err != nil {
return err
}

b, err := ioutil.ReadFile(sbomRef)
if err != nil {
return err
}
s := &cremote.StaticLayer{
B: b,
Mt: types.MediaType(sbomType),
}

img := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
img, err = mutate.Append(img, mutate.Addendum{
Layer: s,
})
if err != nil {
return err
}

// This doesn't work on DockerHub
m, err := img.Manifest()
if err != nil {
return err
}
// Setting it to an artifact type doesn't work on media types
m.Config.MediaType = types.OCIConfigJSON

auth := remote.WithAuthFromKeychain(authn.DefaultKeychain)

get, err := remote.Get(ref, auth)
if err != nil {
return err
}
repo := ref.Context()

dstRef := cosign.AttachedImageTag(repo, get, cosign.SuffixSBOM)

if err := remote.Write(dstRef, img, remote.WithAuthFromKeychain(authn.DefaultKeychain)); err != nil {
return err
}
return nil
}
2 changes: 1 addition & 1 deletion cmd/cosign/cli/download/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func Download() *ffcli.Command {
ShortUsage: "cosign download",
ShortHelp: "download contains tools to download artifacts and attached artifacts in a registry",
FlagSet: flagset,
Subcommands: []*ffcli.Command{Signature()},
Subcommands: []*ffcli.Command{Signature(), SBOM()},
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
Expand Down
90 changes: 90 additions & 0 deletions cmd/cosign/cli/download/sbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// 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 download

import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/sigstore/cosign/pkg/cosign"
)

func SBOM() *ffcli.Command {
var (
flagset = flag.NewFlagSet("cosign download sbom", flag.ExitOnError)
)
return &ffcli.Command{
Name: "sbom",
ShortUsage: "cosign download sbom <image uri>",
ShortHelp: "Download SBOMs from the supplied container image",
FlagSet: flagset,
Exec: func(ctx context.Context, args []string) error {
if len(args) != 1 {
return flag.ErrHelp
}
return SBOMCmd(ctx, args[0])
},
}
}

func SBOMCmd(ctx context.Context, imageRef string) error {
ref, err := name.ParseReference(imageRef)
if err != nil {
return err
}

auth := remote.WithAuthFromKeychain(authn.DefaultKeychain)

get, err := remote.Get(ref, auth)
if err != nil {
return err
}

repo := ref.Context()
dstRef := cosign.AttachedImageTag(repo, get, cosign.SuffixSBOM)
img, err := remote.Image(dstRef, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return err
}
layers, err := img.Layers()
if err != nil {
return err
}
for _, l := range layers {
mt, err := l.MediaType()
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "Found SBOM of media type: %s\n", mt)
r, err := l.Compressed()
if err != nil {
return err
}
sbom, err := ioutil.ReadAll(r)
if err != nil {
return err
}
fmt.Println(string(sbom))
}
return nil
}
7 changes: 4 additions & 3 deletions pkg/cosign/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ type SignedPayload struct {

const (
SuffixSignature = ".sig"
SuffixSBOM = ".sbom"
)

func AttachedImageTag(sigRepo name.Repository, signedImgDesc *remote.Descriptor, suffix string) name.Tag {
func AttachedImageTag(repo name.Repository, imgDesc *remote.Descriptor, suffix string) name.Tag {
// sha256:d34db33f -> sha256-d34db33f.sig
tagStr := strings.ReplaceAll(signedImgDesc.Digest.String(), ":", "-") + suffix
return sigRepo.Tag(tagStr)
tagStr := strings.ReplaceAll(imgDesc.Digest.String(), ":", "-") + suffix
return repo.Tag(tagStr)
}

func GetAttachedManifestForImage(imgDesc *remote.Descriptor, repo name.Repository, suffix string, opts ...remote.Option) (*remote.Descriptor, error) {
Expand Down

0 comments on commit d54af74

Please sign in to comment.