Skip to content

Commit 0f1bf83

Browse files
authored
Add support to download and attach for protobuf bundles (#4477)
--------- Signed-off-by: Zach Steindler <[email protected]>
1 parent 8e7e057 commit 0f1bf83

File tree

12 files changed

+244
-60
lines changed

12 files changed

+244
-60
lines changed

cmd/cosign/cli/attach/attach.go

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
2929
"github.com/sigstore/cosign/v3/pkg/oci/static"
3030
"github.com/sigstore/cosign/v3/pkg/types"
31+
"github.com/sigstore/sigstore-go/pkg/bundle"
3132
)
3233

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

3940
for _, payload := range signedPayloads {
40-
if err := attachAttestation(ctx, ociremoteOpts, payload, imageRef, regOpts.NameOptions()); err != nil {
41+
fmt.Fprintf(os.Stderr, "Using payload from: %s", payload)
42+
43+
ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
44+
if err != nil {
45+
return err
46+
}
47+
if _, ok := ref.(name.Digest); !ok {
48+
ui.Warnf(ctx, ui.TagReferenceMessage, imageRef)
49+
}
50+
51+
digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...)
52+
if err != nil {
53+
return err
54+
}
55+
56+
// Detect if we are using new bundle format
57+
b, err := bundle.LoadJSONFromPath(payload)
58+
if err == nil {
59+
return attachAttestationNewBundle(ociremoteOpts, b, digest)
60+
}
61+
62+
if err := attachAttestation(ociremoteOpts, payload, digest); err != nil {
4163
return fmt.Errorf("attaching payload from %s: %w", payload, err)
4264
}
4365
}
4466

4567
return nil
4668
}
4769

48-
func attachAttestation(ctx context.Context, remoteOpts []ociremote.Option, signedPayload, imageRef string, nameOpts []name.Option) error {
49-
fmt.Fprintf(os.Stderr, "Using payload from: %s", signedPayload)
70+
func attachAttestationNewBundle(remoteOpts []ociremote.Option, b *bundle.Bundle, digest name.Digest) error {
71+
envelope, err := b.Envelope()
72+
if err != nil {
73+
return err
74+
}
75+
if envelope == nil {
76+
return fmt.Errorf("bundle does not have DSSE envelope")
77+
}
78+
statement, err := envelope.Statement()
79+
if err != nil {
80+
return err
81+
}
82+
if statement == nil {
83+
return fmt.Errorf("unable to understand bundle envelope statement")
84+
}
85+
bundleBytes, err := b.MarshalJSON()
86+
if err != nil {
87+
return err
88+
}
89+
return ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, statement.PredicateType, remoteOpts...)
90+
}
91+
92+
func attachAttestation(remoteOpts []ociremote.Option, signedPayload string, digest name.Digest) error {
5093
attestationFile, err := os.Open(signedPayload)
5194
if err != nil {
5295
return err
@@ -73,22 +116,6 @@ func attachAttestation(ctx context.Context, remoteOpts []ociremote.Option, signe
73116
return fmt.Errorf("could not attach attestation without having signatures")
74117
}
75118

76-
ref, err := name.ParseReference(imageRef, nameOpts...)
77-
if err != nil {
78-
return err
79-
}
80-
if _, ok := ref.(name.Digest); !ok {
81-
ui.Warnf(ctx, ui.TagReferenceMessage, imageRef)
82-
}
83-
digest, err := ociremote.ResolveDigest(ref, remoteOpts...)
84-
if err != nil {
85-
return err
86-
}
87-
// Overwrite "ref" with a digest to avoid a race where we use a tag
88-
// multiple times, and it potentially points to different things at
89-
// each access.
90-
ref = digest // nolint
91-
92119
opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
93120
att, err := static.NewAttestation(payload, opts...)
94121
if err != nil {

cmd/cosign/cli/attach/sig.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,10 @@ import (
3030
"github.com/sigstore/cosign/v3/pkg/oci/mutate"
3131
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
3232
"github.com/sigstore/cosign/v3/pkg/oci/static"
33+
sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
3334
)
3435

3536
func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, payloadRef, certRef, certChainRef, timeStampedSigRef, rekorBundleRef, imageRef string) error {
36-
b64SigBytes, err := signatureBytes(sigRef)
37-
if err != nil {
38-
return err
39-
} else if len(b64SigBytes) == 0 {
40-
return errors.New("empty signature")
41-
}
42-
4337
ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
4438
if err != nil {
4539
return err
@@ -52,10 +46,12 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef,
5246
if err != nil {
5347
return err
5448
}
55-
// Overwrite "ref" with a digest to avoid a race where we use a tag
56-
// multiple times, and it potentially points to different things at
57-
// each access.
58-
ref = digest // nolint
49+
50+
// Detect if we are using new bundle format
51+
b, err := sgbundle.LoadJSONFromPath(payloadRef)
52+
if err == nil {
53+
return attachAttestationNewBundle(ociremoteOpts, b, digest)
54+
}
5955

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

66+
b64SigBytes, err := signatureBytes(sigRef)
67+
if err != nil {
68+
return err
69+
} else if len(b64SigBytes) == 0 {
70+
return errors.New("empty signature")
71+
}
72+
7073
sig, err := static.NewSignature(payload, string(b64SigBytes))
7174
if err != nil {
7275
return err

cmd/cosign/cli/download.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func downloadSignature() *cobra.Command {
4949
Args: cobra.ExactArgs(1),
5050
PersistentPreRun: options.BindViper,
5151
RunE: func(cmd *cobra.Command, args []string) error {
52-
return download.SignatureCmd(cmd.Context(), *o, args[0])
52+
return download.SignatureCmd(cmd.Context(), *o, args[0], cmd.OutOrStdout())
5353
},
5454
}
5555

@@ -94,7 +94,7 @@ func downloadAttestation() *cobra.Command {
9494
Args: cobra.ExactArgs(1),
9595
PersistentPreRun: options.BindViper,
9696
RunE: func(cmd *cobra.Command, args []string) error {
97-
return download.AttestationCmd(cmd.Context(), *o, *ao, args[0])
97+
return download.AttestationCmd(cmd.Context(), *o, *ao, args[0], cmd.OutOrStdout())
9898
},
9999
}
100100

cmd/cosign/cli/download/attestation.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"context"
2020
"encoding/json"
2121
"errors"
22-
"fmt"
22+
"io"
2323

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

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

49+
// Try bundles first
50+
newBundles, _, err := cosign.GetBundles(ctx, ref, ociremoteOpts)
51+
if err == nil && len(newBundles) > 0 {
52+
for _, eachBundle := range newBundles {
53+
if predicateType != "" {
54+
envelope, err := eachBundle.Envelope()
55+
if err != nil || envelope == nil {
56+
continue
57+
}
58+
statement, err := envelope.Statement()
59+
if err != nil || statement == nil {
60+
continue
61+
}
62+
if statement.PredicateType != predicateType {
63+
continue
64+
}
65+
}
66+
b, err := json.Marshal(eachBundle)
67+
if err != nil {
68+
return err
69+
}
70+
_, err = out.Write(append(b, byte('\n')))
71+
if err != nil {
72+
return err
73+
}
74+
}
75+
return nil
76+
}
77+
4978
se, err := ociremote.SignedEntity(ref, ociremoteOpts...)
5079
var entityNotFoundError *ociremote.EntityNotFoundError
5180
if err != nil {
@@ -76,7 +105,10 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt
76105
if err != nil {
77106
return err
78107
}
79-
fmt.Println(string(b))
108+
_, err = out.Write(append(b, byte('\n')))
109+
if err != nil {
110+
return err
111+
}
80112
}
81113
return nil
82114
}

cmd/cosign/cli/download/signature.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ package download
1818
import (
1919
"context"
2020
"encoding/json"
21-
"fmt"
21+
"io"
2222

2323
"github.com/google/go-containerregistry/pkg/name"
2424
"github.com/sigstore/cosign/v3/cmd/cosign/cli/options"
2525
"github.com/sigstore/cosign/v3/pkg/cosign"
2626
)
2727

28-
func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string) error {
28+
func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string, out io.Writer) error {
2929
ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...)
3030
if err != nil {
3131
return err
@@ -34,6 +34,23 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef
3434
if err != nil {
3535
return err
3636
}
37+
38+
// Try bundles first
39+
newBundles, _, err := cosign.GetBundles(ctx, ref, ociremoteOpts)
40+
if err == nil && len(newBundles) > 0 {
41+
for _, eachBundle := range newBundles {
42+
b, err := json.Marshal(eachBundle)
43+
if err != nil {
44+
return err
45+
}
46+
_, err = out.Write(append(b, byte('\n')))
47+
if err != nil {
48+
return err
49+
}
50+
}
51+
return nil
52+
}
53+
3754
signatures, err := cosign.FetchSignaturesForReference(ctx, ref, ociremoteOpts...)
3855
if err != nil {
3956
return err
@@ -43,7 +60,10 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef
4360
if err != nil {
4461
return err
4562
}
46-
fmt.Println(string(b))
63+
_, err = out.Write(append(b, byte('\n')))
64+
if err != nil {
65+
return err
66+
}
4767
}
4868
return nil
4969
}

cmd/cosign/cli/options/attach.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) {
4747
cmd.Flags().StringVar(&o.Payload, "payload", "",
4848
"path to the payload covered by the signature")
4949

50+
cmd.Flags().StringVar(&o.Payload, "bundle", "",
51+
"path to bundle containing signature (alias for payload)")
52+
5053
cmd.Flags().StringVar(&o.Cert, "certificate", "",
5154
"path to the X.509 certificate in PEM format to include in the OCI Signature")
5255

cmd/cosign/cli/verify/verify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
137137
if !c.LocalImage {
138138
ref, err := name.ParseReference(images[0], c.NameOptions...)
139139
if err == nil && c.NewBundleFormat {
140-
newBundles, _, err := cosign.GetBundles(ctx, ref, co, c.NameOptions...)
140+
newBundles, _, err := cosign.GetBundles(ctx, ref, co.RegistryClientOpts, c.NameOptions...)
141141
if len(newBundles) == 0 || err != nil {
142142
co.NewBundleFormat = false
143143
}

cmd/cosign/cli/verify/verify_attestation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
122122
if !c.LocalImage {
123123
ref, err := name.ParseReference(images[0], c.NameOptions...)
124124
if err == nil && c.NewBundleFormat {
125-
newBundles, _, err := cosign.GetBundles(ctx, ref, co, c.NameOptions...)
125+
newBundles, _, err := cosign.GetBundles(ctx, ref, co.RegistryClientOpts, c.NameOptions...)
126126
if len(newBundles) == 0 || err != nil {
127127
co.NewBundleFormat = false
128128
}

doc/cosign_attach_signature.md

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cosign/verify.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,10 +1621,10 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name
16211621
return verifySignatures(ctx, sigs, h, co)
16221622
}
16231623

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

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

16691669
// verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles
16701670
func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) {
1671-
bundles, hash, err := GetBundles(ctx, signedImgRef, co, nameOpts...)
1671+
bundles, hash, err := GetBundles(ctx, signedImgRef, co.RegistryClientOpts, nameOpts...)
16721672
if err != nil {
16731673
return nil, false, err
16741674
}

0 commit comments

Comments
 (0)