diff --git a/cmd/cosign/cli/copy.go b/cmd/cosign/cli/copy.go index edae1761267..f6c76571f52 100644 --- a/cmd/cosign/cli/copy.go +++ b/cmd/cosign/cli/copy.go @@ -34,7 +34,10 @@ func Copy() *cobra.Command { cosign copy example.com/src:latest example.com/dest:latest # copy the signatures only - cosign copy --sig-only example.com/src example.com/dest + cosign copy --only=sign example.com/src example.com/dest + + # copy the signatures, attestations, sbom only + cosign copy --only=sign,att,sbom example.com/src example.com/dest # overwrite destination image and signatures cosign copy -f example.com/src example.com/dest @@ -45,7 +48,7 @@ func Copy() *cobra.Command { Args: cobra.ExactArgs(2), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { - return copy.CopyCmd(cmd.Context(), o.Registry, args[0], args[1], o.SignatureOnly, o.Force, o.Platform) + return copy.CopyCmd(cmd.Context(), o.Registry, args[0], args[1], o.SignatureOnly, o.Force, o.CopyOnly, o.Platform) }, } diff --git a/cmd/cosign/cli/copy/copy.go b/cmd/cosign/cli/copy/copy.go index 462c57d282d..65c65e99b30 100644 --- a/cmd/cosign/cli/copy/copy.go +++ b/cmd/cosign/cli/copy/copy.go @@ -21,6 +21,7 @@ import ( "net/http" "os" "runtime" + "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -32,11 +33,12 @@ import ( ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/walk" "golang.org/x/sync/errgroup" + "k8s.io/apimachinery/pkg/util/sets" ) // CopyCmd implements the logic to copy the supplied container image and signatures. // nolint -func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstImg string, sigOnly, force bool, platform string) error { +func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstImg string, sigOnly, force bool, copyOnly, platform string) error { no := regOpts.NameOptions() srcRef, err := name.ParseReference(srcImg, no...) if err != nil { @@ -77,6 +79,7 @@ func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstIm return err } + tags := parseOnlyOpt(copyOnly, sigOnly) if err := walk.SignedEntity(gctx, root, func(ctx context.Context, se oci.SignedEntity) error { // Both of the SignedEntity types implement Digest() h, err := se.Digest() @@ -99,15 +102,7 @@ func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstIm return nil } - if err := copyTag(ociremote.SignatureTag); err != nil { - return err - } - - if sigOnly { - return nil - } - - for _, tm := range []tagMap{ociremote.AttestationTag, ociremote.SBOMTag} { + for _, tm := range tags { if err := copyTag(tm); err != nil { return err } @@ -130,8 +125,8 @@ func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstIm return err } - // If we're only copying sigs, we have nothing left to do. - if sigOnly { + // If we're only copying sig/att/sbom, we have nothing left to do. + if len(tags) > 0 { return nil } @@ -178,3 +173,25 @@ func remoteCopy(ctx context.Context, pusher *remote.Pusher, src, dest name.Refer fmt.Fprintf(os.Stderr, "Copying %s to %s...\n", src, dest) return pusher.Push(ctx, dest, got) } + +func parseOnlyOpt(str string, sigOnly bool) []tagMap { + var tags []tagMap + items := strings.Split(str, ",") + tagSet := sets.New(items...) + + if sigOnly { + fmt.Fprintf(os.Stderr, "--sig-only is deprecated, use --only=sign instead") + tagSet.Insert("sign") + } + + if tagSet.Has("sign") { + tags = append(tags, ociremote.SignatureTag) + } + if tagSet.Has("sbom") { + tags = append(tags, ociremote.SBOMTag) + } + if tagSet.Has("att") { + tags = append(tags, ociremote.AttestationTag) + } + return tags +} diff --git a/cmd/cosign/cli/copy/copy_test.go b/cmd/cosign/cli/copy/copy_test.go index f89c993bfd2..df66dd68463 100644 --- a/cmd/cosign/cli/copy/copy_test.go +++ b/cmd/cosign/cli/copy/copy_test.go @@ -33,7 +33,7 @@ func TestCopyAttachmentTagPrefix(t *testing.T) { err := CopyCmd(ctx, options.RegistryOptions{ RefOpts: refOpts, - }, srcImg, destImg, false, true, "") + }, srcImg, destImg, false, true, "", "") if err == nil { t.Fatal("failed to copy with attachment-tag-prefix") } @@ -45,7 +45,7 @@ func TestCopyPlatformOpt(t *testing.T) { srcImg := "alpine" destImg := "test-alpine" - err := CopyCmd(ctx, options.RegistryOptions{}, srcImg, destImg, false, true, "linux/amd64") + err := CopyCmd(ctx, options.RegistryOptions{}, srcImg, destImg, false, true, "", "linux/amd64") if err == nil { t.Fatal("failed to copy with platform") } diff --git a/cmd/cosign/cli/options/copy.go b/cmd/cosign/cli/options/copy.go index 4e2a99125f9..7f4d5f373ca 100644 --- a/cmd/cosign/cli/options/copy.go +++ b/cmd/cosign/cli/options/copy.go @@ -21,6 +21,7 @@ import ( // CopyOptions is the top level wrapper for the copy command. type CopyOptions struct { + CopyOnly string SignatureOnly bool Force bool Platform string @@ -33,8 +34,11 @@ var _ Interface = (*CopyOptions)(nil) func (o *CopyOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) + cmd.Flags().StringVar(&o.CopyOnly, "only", "", + "custom string array to only copy specific items, this flag is comma delimited. ex: --only=sbom,sign,att") + cmd.Flags().BoolVar(&o.SignatureOnly, "sig-only", false, - "only copy the image signature") + "[DEPRECATED] only copy the image signature") cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "overwrite destination image(s), if necessary") diff --git a/doc/cosign_copy.md b/doc/cosign_copy.md index 2b3bcdc53ba..9cfd71dae34 100644 --- a/doc/cosign_copy.md +++ b/doc/cosign_copy.md @@ -15,7 +15,10 @@ cosign copy [flags] cosign copy example.com/src:latest example.com/dest:latest # copy the signatures only - cosign copy --sig-only example.com/src example.com/dest + cosign copy --only=sign example.com/src example.com/dest + + # copy the signatures, attestations, sbom only + cosign copy --only=sign,att,sbom example.com/src example.com/dest # overwrite destination image and signatures cosign copy -f example.com/src example.com/dest @@ -33,8 +36,9 @@ cosign copy [flags] -f, --force overwrite destination image(s), if necessary -h, --help help for copy --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). + --only string custom string array to only copy specific items, this flag is comma delimited. ex: --only=sbom,sign,att --platform string only copy container image and its signatures for a specific platform image - --sig-only only copy the image signature + --sig-only [DEPRECATED] only copy the image signature ``` ### Options inherited from parent commands