diff --git a/cmd/cosign/cli/download.go b/cmd/cosign/cli/download.go index b19f53765bc..9535d6674dc 100644 --- a/cmd/cosign/cli/download.go +++ b/cmd/cosign/cli/download.go @@ -31,6 +31,7 @@ func Download() *cobra.Command { cmd.AddCommand( downloadSignature(), downloadSBOM(), + downloadAttestation(), ) return cmd @@ -72,3 +73,21 @@ func downloadSBOM() *cobra.Command { return cmd } + +func downloadAttestation() *cobra.Command { + o := &options.RegistryOptions{} + + cmd := &cobra.Command{ + Use: "attestation", + Short: "Download in-toto attestations from the supplied container image", + Example: " cosign download attesation ", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return download.AttestationCmd(cmd.Context(), *o, args[0]) + }, + } + + o.AddFlags(cmd) + + return cmd +} diff --git a/cmd/cosign/cli/download/attestation.go b/cmd/cosign/cli/download/attestation.go new file mode 100644 index 00000000000..938b9df8b52 --- /dev/null +++ b/cmd/cosign/cli/download/attestation.go @@ -0,0 +1,49 @@ +// +// 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" + "encoding/json" + "fmt" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/sigstore/cosign/cmd/cosign/cli/options" + "github.com/sigstore/cosign/pkg/cosign" +) + +func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string) error { + ref, err := name.ParseReference(imageRef) + if err != nil { + return err + } + ociremoteOpts, err := regOpts.ClientOpts(ctx) + if err != nil { + return err + } + attestations, err := cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...) + if err != nil { + return err + } + for _, att := range attestations { + b, err := json.Marshal(att) + if err != nil { + return err + } + fmt.Println(string(b)) + } + return nil +} diff --git a/doc/cosign_download.md b/doc/cosign_download.md index 0e54c56dd62..008bd1b18c5 100644 --- a/doc/cosign_download.md +++ b/doc/cosign_download.md @@ -19,6 +19,7 @@ Provides utilities for downloading artifacts and attached artifacts in a registr ### SEE ALSO * [cosign](cosign.md) - +* [cosign download attestation](cosign_download_attestation.md) - Download in-toto attestations from the supplied container image * [cosign download sbom](cosign_download_sbom.md) - Download SBOMs from the supplied container image * [cosign download signature](cosign_download_signature.md) - Download signatures from the supplied container image diff --git a/doc/cosign_download_attestation.md b/doc/cosign_download_attestation.md new file mode 100644 index 00000000000..9cb073429ce --- /dev/null +++ b/doc/cosign_download_attestation.md @@ -0,0 +1,35 @@ +## cosign download attestation + +Download in-toto attestations from the supplied container image + +``` +cosign download attestation [flags] +``` + +### Examples + +``` + cosign download attesation +``` + +### 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 attestation + --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). +``` + +### Options inherited from parent commands + +``` + --azure-container-registry-config string Path to the file containing Azure container registry configuration information. + --output-file string log output to a file + -d, --verbose log debug output +``` + +### SEE ALSO + +* [cosign download](cosign_download.md) - Provides utilities for downloading artifacts and attached artifacts in a registry + diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index 6986c20c675..7f1ca14dfab 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -18,6 +18,7 @@ package cosign import ( "context" "crypto/x509" + "encoding/json" "fmt" "runtime" @@ -37,6 +38,17 @@ type SignedPayload struct { Bundle *oci.Bundle } +type Signatures struct { + KeyID string `json:"keyid"` + Sig string `json:"sig"` +} + +type AttestationPayload struct { + PayloadType string `json:"payloadType"` + PayLoad string `json:"payload"` + Signatures []Signatures `json:"signatures"` +} + const ( SignatureTagSuffix = ".sig" SBOMTagSuffix = ".sbom" @@ -98,3 +110,41 @@ func FetchSignaturesForReference(ctx context.Context, ref name.Reference, opts . return signatures, nil } + +func FetchAttestationsForReference(ctx context.Context, ref name.Reference, opts ...ociremote.Option) ([]AttestationPayload, error) { + simg, err := ociremote.SignedEntity(ref, opts...) + if err != nil { + return nil, err + } + + atts, err := simg.Attestations() + if err != nil { + return nil, errors.Wrap(err, "remote image") + } + l, err := atts.Get() + if err != nil { + return nil, errors.Wrap(err, "fetching attestations") + } + if len(l) == 0 { + return nil, fmt.Errorf("no attestations associated with %v", ref) + } + + g := pool.New(runtime.NumCPU()) + attestations := make([]AttestationPayload, len(l)) + for i, att := range l { + i, att := i, att + g.Go(func() (err error) { + attestPayload, _ := att.Payload() + err = json.Unmarshal(attestPayload, &attestations[i]) + if err != nil { + return err + } + return err + }) + } + if err := g.Wait(); err != nil { + return nil, err + } + + return attestations, nil +}