From eb29eaf7e9ddf42c1ad15ad6c48121dbb4162331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Batuhan=20Apayd=C4=B1n?= Date: Mon, 14 Mar 2022 12:10:37 +0300 Subject: [PATCH] feat: tree command utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1569 Signed-off-by: Batuhan Apaydın Co-authored-by: Hector Fernandez Signed-off-by: Batuhan Apaydın --- cmd/cosign/cli/commands.go | 1 + cmd/cosign/cli/options/tree.go | 28 ++++++ cmd/cosign/cli/tree.go | 167 +++++++++++++++++++++++++++++++++ doc/cosign.md | 1 + doc/cosign_tree.md | 35 +++++++ pkg/cosign/fetch.go | 6 -- 6 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 cmd/cosign/cli/options/tree.go create mode 100644 cmd/cosign/cli/tree.go create mode 100644 doc/cosign_tree.md diff --git a/cmd/cosign/cli/commands.go b/cmd/cosign/cli/commands.go index 83a6a41a8b7..3bc4a85cfc6 100644 --- a/cmd/cosign/cli/commands.go +++ b/cmd/cosign/cli/commands.go @@ -70,6 +70,7 @@ func New() *cobra.Command { cmd.AddCommand(Attach()) cmd.AddCommand(Attest()) cmd.AddCommand(Clean()) + cmd.AddCommand(Tree()) cmd.AddCommand(Completion()) cmd.AddCommand(Copy()) cmd.AddCommand(Dockerfile()) diff --git a/cmd/cosign/cli/options/tree.go b/cmd/cosign/cli/options/tree.go new file mode 100644 index 00000000000..a015d8949a5 --- /dev/null +++ b/cmd/cosign/cli/options/tree.go @@ -0,0 +1,28 @@ +// Copyright 2022 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 options + +import "github.com/spf13/cobra" + +type TreeOptions struct { + Registry RegistryOptions + CleanType string +} + +var _ Interface = (*TreeOptions)(nil) + +func (c *TreeOptions) AddFlags(cmd *cobra.Command) { + c.Registry.AddFlags(cmd) +} diff --git a/cmd/cosign/cli/tree.go b/cmd/cosign/cli/tree.go new file mode 100644 index 00000000000..abbdc59afb5 --- /dev/null +++ b/cmd/cosign/cli/tree.go @@ -0,0 +1,167 @@ +// +// 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" + "fmt" + "os" + "strings" + + v1 "github.com/google/go-containerregistry/pkg/v1" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/spf13/cobra" + + "github.com/sigstore/cosign/cmd/cosign/cli/options" + ociremote "github.com/sigstore/cosign/pkg/oci/remote" +) + +func Tree() *cobra.Command { + c := &options.TreeOptions{} + + cmd := &cobra.Command{ + Use: "tree", + Short: "Display supply chain security related artifacts for an image such as signatures, SBOMs and attestations", + Example: " cosign tree ", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return TreeCmd(cmd.Context(), c.Registry, args[0]) + }, + } + + c.AddFlags(cmd) + return cmd +} + +const ( + SignatureTagSuffix = ".sig" + SBOMTagSuffix = ".sbom" + AttestationTagSuffix = ".att" +) + +func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string) error { + scsaMap := map[name.Tag][]v1.Layer{} + ref, err := name.ParseReference(imageRef) + if err != nil { + return err + } + + remoteOpts, err := regOpts.ClientOpts(ctx) + if err != nil { + return err + } + fmt.Fprintf(os.Stdout, "📦 Supply Chain Security Related artifacts for an image: %s\n", ref.String()) + + simg, err := ociremote.SignedEntity(ref, remoteOpts...) + if err != nil { + return err + } + + registryClientOpts := regOpts.GetRegistryClientOpts(ctx) + + attRef, err := ociremote.AttestationTag(ref, ociremote.WithRemoteOptions(registryClientOpts...)) + if err != nil { + return err + } + + atts, err := simg.Attestations() + var attLayers []v1.Layer + if err == nil { + layers, err := atts.Layers() + if err != nil { + return err + } + attLayers = append(attLayers, layers...) + } + + scsaMap[attRef] = attLayers + + sigRef, err := ociremote.SignatureTag(ref, ociremote.WithRemoteOptions(registryClientOpts...)) + if err != nil { + return err + } + + sigs, err := simg.Signatures() + var sigLayers []v1.Layer + if err == nil { + layers, err := sigs.Layers() + if err != nil { + return err + } + sigLayers = append(sigLayers, layers...) + } + + scsaMap[sigRef] = sigLayers + + sbomRef, err := ociremote.SBOMTag(ref, ociremote.WithRemoteOptions(registryClientOpts...)) + if err != nil { + return err + } + + sbombs, err := simg.Attachment("sbom") + var sbomLayers []v1.Layer + if err == nil { + layers, err := sbombs.Layers() + if err != nil { + return err + } + sbomLayers = append(sbomLayers, layers...) + } + + scsaMap[sbomRef] = sbomLayers + + if len(scsaMap) == 0 { + fmt.Fprintf(os.Stdout, "No Supply Chain Security Related Artifacts artifacts found for image %s\n, start creating one with simply running"+ + "$ COSIGN_EXPERIMENTAL=1 cosign sign ", ref.String()) + return nil + } + + for t, k := range scsaMap { + switch { + case strings.HasSuffix(t.TagStr(), SignatureTagSuffix): + fmt.Fprintf(os.Stdout, "└── 🔐 Signatures for an image tag: %s\n", t.String()) + case strings.HasSuffix(t.TagStr(), SBOMTagSuffix): + fmt.Fprintf(os.Stdout, "└── 📦 SBOMs for an image tag: %s\n", t.String()) + case strings.HasSuffix(t.TagStr(), AttestationTagSuffix): + fmt.Fprintf(os.Stdout, "└── 💾 Attestations for an image tag: %s\n", t.String()) + } + + if err := printLayers(k); err != nil { + return err + } + } + + return nil +} + +func printLayers(layers []v1.Layer) error { + for i, l := range layers { + last := i == len(layers)-1 + var sym string + if last { + sym = " └──" + } else { + sym = " ├──" + } + digest, err := l.Digest() + if err != nil { + return err + } + fmt.Printf("%s 🍒 %s\n", sym, digest) + } + return nil +} diff --git a/doc/cosign.md b/doc/cosign.md index b202709663e..5e4b931d084 100644 --- a/doc/cosign.md +++ b/doc/cosign.md @@ -34,6 +34,7 @@ * [cosign save](cosign_save.md) - Save the container image and associated signatures to disk at the specified directory. * [cosign sign](cosign_sign.md) - Sign the supplied container image. * [cosign sign-blob](cosign_sign-blob.md) - Sign the supplied blob, outputting the base64-encoded signature to stdout. +* [cosign tree](cosign_tree.md) - Display supply chain security related artifacts for an image such as signatures, SBOMs and attestations * [cosign triangulate](cosign_triangulate.md) - Outputs the located cosign image reference. This is the location cosign stores the specified artifact type. * [cosign upload](cosign_upload.md) - Provides utilities for uploading artifacts to a registry * [cosign verify](cosign_verify.md) - Verify a signature on the supplied container image diff --git a/doc/cosign_tree.md b/doc/cosign_tree.md new file mode 100644 index 00000000000..6d5bc0fd41b --- /dev/null +++ b/doc/cosign_tree.md @@ -0,0 +1,35 @@ +## cosign tree + +Display supply chain security related artifacts for an image such as signatures, SBOMs and attestations + +``` +cosign tree [flags] +``` + +### Examples + +``` + cosign tree +``` + +### Options + +``` + --allow-insecure-registry whether to allow insecure connections to registries. Don't use this for anything but testing + --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] + -h, --help help for tree + --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). +``` + +### Options inherited from parent commands + +``` + --output-file string log output to a file + -t, --timeout duration timeout for commands (default 3m0s) + -d, --verbose log debug output +``` + +### SEE ALSO + +* [cosign](cosign.md) - + diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index 04bad1744e5..80b85762bfd 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -55,12 +55,6 @@ type AttestationPayload struct { Signatures []Signatures `json:"signatures"` } -const ( - SignatureTagSuffix = ".sig" - SBOMTagSuffix = ".sbom" - AttestationTagSuffix = ".att" -) - const ( Signature = "signature" SBOM = "sbom"