Skip to content

Commit

Permalink
implement cosign copy
Browse files Browse the repository at this point in the history
Signed-off-by: Jake Sanders <jsand@google.com>
  • Loading branch information
Jake Sanders committed May 11, 2021
1 parent 3f93f8a commit b2253cd
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 1 deletion.
127 changes: 127 additions & 0 deletions cmd/cosign/cli/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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 cli

import (
"context"
"flag"
"fmt"

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

func Copy() *ffcli.Command {
var (
flagset = flag.NewFlagSet("cosign copy", flag.ExitOnError)
sigOnlyFlag = flagset.Bool("sig-only", false, "only copy the image signature")
forceFlag = flagset.Bool("f", false, "overwrite destination image(s), if necessary")
)
return &ffcli.Command{
Name: "copy",
ShortUsage: "cosign copy <source image> <destination image>",
ShortHelp: `Copy the supplied container image and signatures.`,
LongHelp: `Copy the supplied container image and signatures.
EXAMPLES
# copy a container image and its signatures
cosign copy example.com/src:latest example.com/dest:latest
# copy the signatures only
cosign copy -sig-only example.com/src example.com/dest
# overwrite destination image and signatures
cosign copy -f example.com/src example.com/dest
`,
FlagSet: flagset,
Exec: func(ctx context.Context, args []string) error {
if len(args) != 2 {
return flag.ErrHelp
}
return CopyCmd(ctx, args[0], args[1], *sigOnlyFlag, *forceFlag)
},
}
}

func CopyCmd(ctx context.Context, srcImg, dstImg string, sigOnly, force bool) error {
remoteAuth := remote.WithAuthFromKeychain(authn.DefaultKeychain)

srcRef, err := name.ParseReference(srcImg)
if err != nil {
return err
}
dstRef, err := name.ParseReference(dstImg)
if err != nil {
return err
}

gotSrc, err := remote.Get(srcRef, remoteAuth)
if err != nil {
return err
}

sigSrcRef, err := cosign.DestinationRef(srcRef, gotSrc)
if err != nil {
return err
}

dstRepoRef := dstRef.Context()
sigDstRef := dstRepoRef.Tag(sigSrcRef.Identifier())

if err := copyImage(sigSrcRef, sigDstRef, force, remoteAuth); err != nil {
return err
}

if !sigOnly {
if err := copyImage(srcRef, dstRef, force, remoteAuth); err != nil {
return err
}
}

return nil
}

func descriptorsEqual(a, b *v1.Descriptor) bool {
if a == nil || b == nil {
return a == nil && b == nil
}
return a.Digest == b.Digest
}

func copyImage(src, dest name.Reference, overwrite bool, opts ...remote.Option) error {
got, err := remote.Get(src, opts...)
if err != nil {
return err
}
img, err := got.Image()
if err != nil {
return err
}

if !overwrite {
if dstDesc, err := remote.Head(dest, opts...); err == nil {
if descriptorsEqual(&got.Descriptor, dstDesc) {
return nil
}
return fmt.Errorf("image %q already exists. Use `-f` to overwrite", dest.Name())
}
}

return remote.Write(dest, img, opts...)
}
2 changes: 1 addition & 1 deletion cmd/cosign/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func main() {
FlagSet: rootFlagSet,
Subcommands: []*ffcli.Command{
cli.Verify(), cli.Sign(), cli.Upload(), cli.Generate(), cli.Download(), cli.GenerateKeyPair(), cli.SignBlob(),
cli.VerifyBlob(), cli.Triangulate(), cli.Version(), cli.PublicKey(), pivcli.PivKey()},
cli.VerifyBlob(), cli.Triangulate(), cli.Version(), cli.PublicKey(), pivcli.PivKey(), cli.Copy()},
Exec: func(context.Context, []string) error {
return flag.ErrHelp
},
Expand Down
6 changes: 6 additions & 0 deletions test/e2e_test_secrets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,23 @@ export COSIGN_PASSWORD=$pass
img="us-central1-docker.pkg.dev/projectsigstore/cosign-ci/test"
img2="us-central1-docker.pkg.dev/projectsigstore/cosign-ci/test-2"
legacy_img="us-central1-docker.pkg.dev/projectsigstore/cosign-ci/legacy-test"
img_copy="${img}/copy"
for image in $img $img2 $legacy_img
do
(crane delete $(./cosign triangulate $image)) || true
crane cp busybox $image
done
crane ls $img_copy | while read tag ; do crane delete $tag ; done


## sign/verify
./cosign sign -key cosign.key $img
./cosign verify -key cosign.pub $img

# copy
./cosign copy -source $img -destination $img_copy
./cosign verify -key cosign.pub $img_copy

## confirm use of OCI media type in signature image
crane manifest $(./cosign triangulate $img) | grep -q "application/vnd.oci.image.config.v1+json"

Expand Down

0 comments on commit b2253cd

Please sign in to comment.