Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions cmd/cosign/cli/attach/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
"github.com/sigstore/cosign/v3/pkg/oci/static"
"github.com/sigstore/cosign/v3/pkg/types"
"github.com/sigstore/sigstore-go/pkg/bundle"
)

func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, signedPayloads []string, imageRef string) error {
Expand All @@ -37,16 +38,58 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, signed
}

for _, payload := range signedPayloads {
if err := attachAttestation(ctx, ociremoteOpts, payload, imageRef, regOpts.NameOptions()); err != nil {
fmt.Fprintf(os.Stderr, "Using payload from: %s", payload)

ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
if err != nil {
return err
}
if _, ok := ref.(name.Digest); !ok {
ui.Warnf(ctx, ui.TagReferenceMessage, imageRef)
}

digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...)
if err != nil {
return err
}

// Detect if we are using new bundle format
b, err := bundle.LoadJSONFromPath(payload)
if err == nil {
return attachAttestationNewBundle(ociremoteOpts, b, digest)
}

if err := attachAttestation(ociremoteOpts, payload, digest); err != nil {
return fmt.Errorf("attaching payload from %s: %w", payload, err)
}
}

return nil
}

func attachAttestation(ctx context.Context, remoteOpts []ociremote.Option, signedPayload, imageRef string, nameOpts []name.Option) error {
fmt.Fprintf(os.Stderr, "Using payload from: %s", signedPayload)
func attachAttestationNewBundle(remoteOpts []ociremote.Option, b *bundle.Bundle, digest name.Digest) error {
envelope, err := b.Envelope()
if err != nil {
return err
}
if envelope == nil {
return fmt.Errorf("bundle does not have DSSE envelope")
}
statement, err := envelope.Statement()
if err != nil {
return err
}
if statement == nil {
return fmt.Errorf("unable to understand bundle envelope statement")
}
bundleBytes, err := b.MarshalJSON()
if err != nil {
return err
}
return ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, statement.PredicateType, remoteOpts...)
}

func attachAttestation(remoteOpts []ociremote.Option, signedPayload string, digest name.Digest) error {
attestationFile, err := os.Open(signedPayload)
if err != nil {
return err
Expand All @@ -73,22 +116,6 @@ func attachAttestation(ctx context.Context, remoteOpts []ociremote.Option, signe
return fmt.Errorf("could not attach attestation without having signatures")
}

ref, err := name.ParseReference(imageRef, nameOpts...)
if err != nil {
return err
}
if _, ok := ref.(name.Digest); !ok {
ui.Warnf(ctx, ui.TagReferenceMessage, imageRef)
}
digest, err := ociremote.ResolveDigest(ref, remoteOpts...)
if err != nil {
return err
}
// Overwrite "ref" with a digest to avoid a race where we use a tag
// multiple times, and it potentially points to different things at
// each access.
ref = digest // nolint

opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
att, err := static.NewAttestation(payload, opts...)
if err != nil {
Expand Down
25 changes: 14 additions & 11 deletions cmd/cosign/cli/attach/sig.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,10 @@ import (
"github.com/sigstore/cosign/v3/pkg/oci/mutate"
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
"github.com/sigstore/cosign/v3/pkg/oci/static"
sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
)

func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, payloadRef, certRef, certChainRef, timeStampedSigRef, rekorBundleRef, imageRef string) error {
b64SigBytes, err := signatureBytes(sigRef)
if err != nil {
return err
} else if len(b64SigBytes) == 0 {
return errors.New("empty signature")
}

ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
if err != nil {
return err
Expand All @@ -52,10 +46,12 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef,
if err != nil {
return err
}
// Overwrite "ref" with a digest to avoid a race where we use a tag
// multiple times, and it potentially points to different things at
// each access.
ref = digest // nolint

// Detect if we are using new bundle format
b, err := sgbundle.LoadJSONFromPath(payloadRef)
if err == nil {
return attachAttestationNewBundle(ociremoteOpts, b, digest)
}

var payload []byte
if payloadRef == "" {
Expand All @@ -67,6 +63,13 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef,
return err
}

b64SigBytes, err := signatureBytes(sigRef)
if err != nil {
return err
} else if len(b64SigBytes) == 0 {
return errors.New("empty signature")
}

sig, err := static.NewSignature(payload, string(b64SigBytes))
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions cmd/cosign/cli/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func downloadSignature() *cobra.Command {
Args: cobra.ExactArgs(1),
PersistentPreRun: options.BindViper,
RunE: func(cmd *cobra.Command, args []string) error {
return download.SignatureCmd(cmd.Context(), *o, args[0])
return download.SignatureCmd(cmd.Context(), *o, args[0], cmd.OutOrStdout())
},
}

Expand Down Expand Up @@ -94,7 +94,7 @@ func downloadAttestation() *cobra.Command {
Args: cobra.ExactArgs(1),
PersistentPreRun: options.BindViper,
RunE: func(cmd *cobra.Command, args []string) error {
return download.AttestationCmd(cmd.Context(), *o, *ao, args[0])
return download.AttestationCmd(cmd.Context(), *o, *ao, args[0], cmd.OutOrStdout())
},
}

Expand Down
38 changes: 35 additions & 3 deletions cmd/cosign/cli/download/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"io"

"github.com/google/go-containerregistry/pkg/name"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/options"
Expand All @@ -28,7 +28,7 @@ import (
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
)

func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOptions options.AttestationDownloadOptions, imageRef string) error {
func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOptions options.AttestationDownloadOptions, imageRef string, out io.Writer) error {
ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
if err != nil {
return err
Expand All @@ -46,6 +46,35 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt
}
}

// Try bundles first
newBundles, _, err := cosign.GetBundles(ctx, ref, ociremoteOpts)
if err == nil && len(newBundles) > 0 {
for _, eachBundle := range newBundles {
if predicateType != "" {
envelope, err := eachBundle.Envelope()
if err != nil || envelope == nil {
continue
}
statement, err := envelope.Statement()
if err != nil || statement == nil {
continue
}
if statement.PredicateType != predicateType {
continue
}
}
b, err := json.Marshal(eachBundle)
if err != nil {
return err
}
_, err = out.Write(append(b, byte('\n')))
if err != nil {
return err
}
}
return nil
}

se, err := ociremote.SignedEntity(ref, ociremoteOpts...)
var entityNotFoundError *ociremote.EntityNotFoundError
if err != nil {
Expand Down Expand Up @@ -76,7 +105,10 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt
if err != nil {
return err
}
fmt.Println(string(b))
_, err = out.Write(append(b, byte('\n')))
if err != nil {
return err
}
}
return nil
}
26 changes: 23 additions & 3 deletions cmd/cosign/cli/download/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ package download
import (
"context"
"encoding/json"
"fmt"
"io"

"github.com/google/go-containerregistry/pkg/name"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v3/pkg/cosign"
)

func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string) error {
func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string, out io.Writer) error {
ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
if err != nil {
return err
Expand All @@ -34,6 +34,23 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef
if err != nil {
return err
}

// Try bundles first
newBundles, _, err := cosign.GetBundles(ctx, ref, ociremoteOpts)
if err == nil && len(newBundles) > 0 {
for _, eachBundle := range newBundles {
b, err := json.Marshal(eachBundle)
if err != nil {
return err
}
_, err = out.Write(append(b, byte('\n')))
if err != nil {
return err
}
}
return nil
}

signatures, err := cosign.FetchSignaturesForReference(ctx, ref, ociremoteOpts...)
if err != nil {
return err
Expand All @@ -43,7 +60,10 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef
if err != nil {
return err
}
fmt.Println(string(b))
_, err = out.Write(append(b, byte('\n')))
if err != nil {
return err
}
}
return nil
}
3 changes: 3 additions & 0 deletions cmd/cosign/cli/options/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Payload, "payload", "",
"path to the payload covered by the signature")

cmd.Flags().StringVar(&o.Payload, "bundle", "",
"path to bundle containing signature (alias for payload)")

cmd.Flags().StringVar(&o.Cert, "certificate", "",
"path to the X.509 certificate in PEM format to include in the OCI Signature")

Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
if !c.LocalImage {
ref, err := name.ParseReference(images[0], c.NameOptions...)
if err == nil && c.NewBundleFormat {
newBundles, _, err := cosign.GetBundles(ctx, ref, co, c.NameOptions...)
newBundles, _, err := cosign.GetBundles(ctx, ref, co.RegistryClientOpts, c.NameOptions...)
if len(newBundles) == 0 || err != nil {
co.NewBundleFormat = false
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
if !c.LocalImage {
ref, err := name.ParseReference(images[0], c.NameOptions...)
if err == nil && c.NewBundleFormat {
newBundles, _, err := cosign.GetBundles(ctx, ref, co, c.NameOptions...)
newBundles, _, err := cosign.GetBundles(ctx, ref, co.RegistryClientOpts, c.NameOptions...)
if len(newBundles) == 0 || err != nil {
co.NewBundleFormat = false
}
Expand Down
1 change: 1 addition & 0 deletions doc/cosign_attach_signature.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -1621,10 +1621,10 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name
return verifySignatures(ctx, sigs, h, co)
}

func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) ([]*sgbundle.Bundle, *v1.Hash, error) {
func GetBundles(_ context.Context, signedImgRef name.Reference, registryClientOpts []ociremote.Option, nameOpts ...name.Option) ([]*sgbundle.Bundle, *v1.Hash, error) {
// This is a carefully optimized sequence for fetching the signatures of the
// entity that minimizes registry requests when supplied with a digest input
digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
digest, err := ociremote.ResolveDigest(signedImgRef, registryClientOpts...)
if err != nil {
if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound {
return nil, nil, &ErrImageTagNotFound{
Expand All @@ -1638,7 +1638,7 @@ func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts, n
return nil, nil, err
}

index, err := ociremote.Referrers(digest, "", co.RegistryClientOpts...)
index, err := ociremote.Referrers(digest, "", registryClientOpts...)
if err != nil {
return nil, nil, err
}
Expand All @@ -1648,7 +1648,7 @@ func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts, n
if err != nil {
return nil, nil, err
}
bundle, err := ociremote.Bundle(st, co.RegistryClientOpts...)
bundle, err := ociremote.Bundle(st, registryClientOpts...)
if err != nil {
// There may be non-Sigstore referrers in the index, so we can ignore them.
// TODO: Should we surface any errors here (e.g. if the bundle is invalid)?
Expand All @@ -1668,7 +1668,7 @@ func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts, n

// verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles
func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) {
bundles, hash, err := GetBundles(ctx, signedImgRef, co, nameOpts...)
bundles, hash, err := GetBundles(ctx, signedImgRef, co.RegistryClientOpts, nameOpts...)
if err != nil {
return nil, false, err
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/cosign/verify_oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestGetBundles_Empty(t *testing.T) {
assert.NoError(t, err)

// If tag doesn't exist, should return ErrImageTagNotFound
bundles, hash, err := GetBundles(context.Background(), ref, &CheckOpts{})
bundles, hash, err := GetBundles(context.Background(), ref, []ociremote.Option{})
imgTagNotFound := &ErrImageTagNotFound{}
assert.ErrorAs(t, err, &imgTagNotFound)
assert.Len(t, bundles, 0)
Expand All @@ -65,7 +65,7 @@ func TestGetBundles_Empty(t *testing.T) {
assert.NoError(t, remote.Write(ref, img))

// Check that no matching attestation error is returned
bundles, hash, err = GetBundles(context.Background(), ref, &CheckOpts{})
bundles, hash, err = GetBundles(context.Background(), ref, []ociremote.Option{})
var noMatchErr *ErrNoMatchingAttestations
assert.ErrorAs(t, err, &noMatchErr)
assert.Len(t, bundles, 0)
Expand All @@ -81,7 +81,7 @@ func TestGetBundles_Empty(t *testing.T) {
assert.NoError(t, err)

// Should still return no matching attestation error, as it failed to parse the bundle
bundles, hash, err = GetBundles(context.Background(), ref, &CheckOpts{})
bundles, hash, err = GetBundles(context.Background(), ref, []ociremote.Option{})
assert.ErrorAs(t, err, &noMatchErr)
assert.Len(t, bundles, 0)
assert.Nil(t, hash)
Expand Down Expand Up @@ -111,7 +111,7 @@ func TestGetBundles_Valid(t *testing.T) {
assert.NoError(t, err)

// Retrieve the attestation
bundles, hash, err := GetBundles(context.Background(), ref, &CheckOpts{})
bundles, hash, err := GetBundles(context.Background(), ref, []ociremote.Option{})
assert.NoError(t, err)
assert.Len(t, bundles, 1)
assert.NotNil(t, hash)
Expand Down
Loading
Loading