Skip to content

Commit f473bb1

Browse files
committed
Add support for Sigstore Bundles using sigstore-go verifier (#151)
* Remove dependabot for this fork (#159) * Add Actions release and attest job (#147) * update release workflow Signed-off-by: Meredith Lancaster <malancas@github.com> * Grab image digest for attestation step Signed-off-by: Meredith Lancaster <malancas@github.com> * comment Signed-off-by: Meredith Lancaster <malancas@github.com> * update workflow name Signed-off-by: Meredith Lancaster <malancas@github.com> * add release directions Signed-off-by: Meredith Lancaster <malancas@github.com> * undo ko config changes Signed-off-by: Meredith Lancaster <malancas@github.com> * add fork specific options to ko build call Signed-off-by: Meredith Lancaster <malancas@github.com> * Change version format --------- Signed-off-by: Meredith Lancaster <malancas@github.com> Co-authored-by: Cody Soyland <codysoyland@github.com> * set release as target branch (#161) Signed-off-by: Meredith Lancaster <malancas@github.com> * Add support for Sigstore Bundles using sigstore-go verifier Signed-off-by: Cody Soyland <codysoyland@github.com> * Update docs Signed-off-by: Cody Soyland <codysoyland@github.com> * Rename func Signed-off-by: Cody Soyland <codysoyland@github.com> * Comment on observe timestamp setting Signed-off-by: Cody Soyland <codysoyland@github.com> * Refactor trusted material, add support for default TUF repo in bundle verifier Signed-off-by: Cody Soyland <codysoyland@github.com> * Remove accidental code Signed-off-by: Cody Soyland <codysoyland@github.com> * Fix tlog verification options Signed-off-by: Cody Soyland <codysoyland@github.com> --------- Signed-off-by: Meredith Lancaster <malancas@github.com> Signed-off-by: Cody Soyland <codysoyland@github.com> Co-authored-by: Meredith Lancaster <malancas@users.noreply.github.com> Fix method name Signed-off-by: Cody Soyland <codysoyland@github.com>
1 parent 0abb6d4 commit f473bb1

File tree

11 files changed

+311
-14
lines changed

11 files changed

+311
-14
lines changed

config/300-clusterimagepolicy.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ spec:
209209
trustRootRef:
210210
description: Use the Certificate Chain from the referred TrustRoot.TimeStampAuthorities
211211
type: string
212+
signatureFormat:
213+
description: SignatureFormat specifies the format the authority expects. Supported formats are "simplesigning" and "bundle". If not specified, the default is "simplesigning" (cosign's default).
214+
type: string
212215
source:
213216
description: Sources sets the configuration to specify the sources from where to consume the signatures.
214217
type: array
@@ -545,6 +548,9 @@ spec:
545548
trustRootRef:
546549
description: Use the Certificate Chain from the referred TrustRoot.TimeStampAuthorities
547550
type: string
551+
signatureFormat:
552+
description: SignatureFormat specifies the format the authority expects. Supported formats are "simplesigning" and "bundle". If not specified, the default is "simplesigning" (cosign's default).
553+
type: string
548554
source:
549555
description: Sources sets the configuration to specify the sources from where to consume the signatures.
550556
type: array

docs/api-types/index-v1alpha1.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Attestation defines the type of attestation to validate and optionally apply a p
172172
| ctlog | CTLog sets the configuration to verify the authority against a Rekor instance. | [TLog](#tlog) | false |
173173
| attestations | Attestations is a list of individual attestations for this authority, once the signature for this authority has been verified. | [][Attestation](#attestation) | false |
174174
| rfc3161timestamp | RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance. | [RFC3161Timestamp](#rfc3161timestamp) | false |
175+
| signatureFormat | SignatureFormat specifies the format the authority expects. Supported formats are \"simplesigning\" and \"bundle\". If not specified, the default is \"simplesigning\" (cosign's default). | string | false |
175176

176177
[Back to TOC](#table-of-contents)
177178

docs/api-types/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ The authorities block defines the rules for discovering and validating signature
4949
| ctlog | CTLog sets the configuration to verify the authority against a Rekor instance. | [TLog](#tlog) | false |
5050
| attestations | Attestations is a list of individual attestations for this authority, once the signature for this authority has been verified. | [][Attestation](#attestation) | false |
5151
| rfc3161timestamp | RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance. | [RFC3161Timestamp](#rfc3161timestamp) | false |
52+
| signatureFormat | SignatureFormat specifies the format the authority expects. Supported formats are \"simplesigning\" and \"bundle\". If not specified, the default is \"simplesigning\" (cosign's default). | string | false |
5253

5354
[Back to TOC](#table-of-contents)
5455

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ require (
6464
github.com/go-jose/go-jose/v4 v4.0.5
6565
github.com/sigstore/protobuf-specs v0.4.1
6666
github.com/sigstore/scaffolding v0.7.22
67+
github.com/sigstore/sigstore-go v0.7.1
6768
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.1
6869
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.1
6970
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.1
@@ -228,7 +229,6 @@ require (
228229
github.com/sassoftware/relic v7.2.1+incompatible // indirect
229230
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
230231
github.com/shibumi/go-pathspec v1.3.0 // indirect
231-
github.com/sigstore/sigstore-go v0.7.1 // indirect
232232
github.com/sigstore/timestamp-authority v1.2.5 // indirect
233233
github.com/sirupsen/logrus v1.9.3 // indirect
234234
github.com/sourcegraph/conc v0.3.0 // indirect

pkg/apis/policy/v1alpha1/clusterimagepolicy_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ type Authority struct {
144144
// RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance.
145145
// +optional
146146
RFC3161Timestamp *RFC3161Timestamp `json:"rfc3161timestamp,omitempty"`
147+
// SignatureFormat specifies the format the authority expects. Supported
148+
// formats are "simplesigning" and "bundle". If not specified, the default
149+
// is "simplesigning" (cosign's default).
150+
SignatureFormat string `json:"signatureFormat,omitempty"`
147151
}
148152

149153
// This references a public verification key stored in

pkg/apis/policy/v1beta1/clusterimagepolicy_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ type Authority struct {
143143
// RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance.
144144
// +optional
145145
RFC3161Timestamp *RFC3161Timestamp `json:"rfc3161timestamp,omitempty"`
146+
// SignatureFormat specifies the format the authority expects. Supported
147+
// formats are "simplesigning" and "bundle". If not specified, the default
148+
// is "simplesigning" (cosign's default).
149+
SignatureFormat string `json:"signatureFormat,omitempty"`
146150
}
147151

148152
// This references a public verification key stored in

pkg/tuf/repo.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ import (
2828
"path/filepath"
2929
"runtime"
3030
"strings"
31+
"sync"
3132
"testing/fstest"
3233
"time"
3334

35+
"github.com/sigstore/sigstore-go/pkg/root"
36+
"github.com/sigstore/sigstore/pkg/tuf"
3437
"github.com/theupdateframework/go-tuf/client"
3538
"sigs.k8s.io/release-utils/version"
3639
)
@@ -294,3 +297,31 @@ func ClientFromRemote(_ context.Context, mirror string, rootJSON []byte, targets
294297
}
295298
return tufClient, nil
296299
}
300+
301+
var (
302+
once sync.Once
303+
trustedRoot *root.TrustedRoot
304+
singletonRootError error
305+
)
306+
307+
// GetTrustedRoot returns the trusted root for the TUF repository.
308+
func GetTrustedRoot() (*root.TrustedRoot, error) {
309+
once.Do(func() {
310+
tufClient, err := tuf.NewFromEnv(context.Background())
311+
if err != nil {
312+
singletonRootError = fmt.Errorf("initializing tuf: %w", err)
313+
return
314+
}
315+
// TODO: add support for custom trusted root path
316+
targetBytes, err := tufClient.GetTarget("trusted_root.json")
317+
if err != nil {
318+
singletonRootError = fmt.Errorf("error getting targets: %w", err)
319+
return
320+
}
321+
trustedRoot, singletonRootError = root.NewTrustedRootFromJSON(targetBytes)
322+
})
323+
if singletonRootError != nil {
324+
return nil, singletonRootError
325+
}
326+
return trustedRoot, nil
327+
}

pkg/webhook/bundle.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package webhook
2+
3+
import (
4+
"crypto/x509"
5+
"encoding/hex"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"io"
10+
"strings"
11+
12+
"github.com/google/go-containerregistry/pkg/name"
13+
v1 "github.com/google/go-containerregistry/pkg/v1"
14+
"github.com/google/go-containerregistry/pkg/v1/remote"
15+
16+
"github.com/sigstore/sigstore-go/pkg/bundle"
17+
"github.com/sigstore/sigstore-go/pkg/root"
18+
"github.com/sigstore/sigstore-go/pkg/verify"
19+
)
20+
21+
type VerifiedBundle struct {
22+
SGBundle *bundle.Bundle
23+
Result *verify.VerificationResult
24+
Hash v1.Hash
25+
}
26+
27+
// VerifiedBundle implements Signature
28+
var _ Signature = &VerifiedBundle{}
29+
30+
func (vb *VerifiedBundle) Digest() (v1.Hash, error) {
31+
return vb.Hash, nil
32+
}
33+
34+
func (vb *VerifiedBundle) Payload() ([]byte, error) {
35+
// todo: this should return the json-serialized dsse envelope
36+
envelope := vb.SGBundle.GetDsseEnvelope()
37+
if envelope == nil {
38+
return nil, fmt.Errorf("no dsse envelope found")
39+
}
40+
return json.Marshal(envelope)
41+
}
42+
43+
func (vb *VerifiedBundle) Signature() ([]byte, error) {
44+
// TODO: implement this
45+
return []byte{}, nil
46+
}
47+
48+
func (vb *VerifiedBundle) Cert() (*x509.Certificate, error) {
49+
vc, err := vb.SGBundle.VerificationContent()
50+
if err != nil {
51+
return nil, err
52+
}
53+
cert := vc.Certificate()
54+
if cert != nil {
55+
return cert, nil
56+
}
57+
return nil, errors.New("bundle does not contain a certificate")
58+
}
59+
60+
func VerifiedBundles(ref name.Reference, trustedMaterial root.TrustedMaterial, remoteOpts []remote.Option, policyOptions []verify.PolicyOption, verifierOptions []verify.VerifierOption) ([]Signature, error) {
61+
sev, err := verify.NewSignedEntityVerifier(trustedMaterial, verifierOptions...)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
bundles, hash, err := getBundles(ref, remoteOpts)
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
digestBytes, err := hex.DecodeString(hash.Hex)
72+
if err != nil {
73+
return nil, err
74+
}
75+
artifactPolicy := verify.WithArtifactDigest(hash.Algorithm, digestBytes)
76+
policy := verify.NewPolicy(artifactPolicy, policyOptions...)
77+
78+
verifiedBundles := make([]Signature, 0)
79+
for _, b := range bundles {
80+
// TODO: should these be done in parallel? (as is done in cosign?)
81+
result, err := sev.Verify(b, policy)
82+
if err == nil {
83+
verifiedBundles = append(verifiedBundles, &VerifiedBundle{SGBundle: b, Result: result, Hash: *hash})
84+
}
85+
}
86+
return verifiedBundles, nil
87+
}
88+
89+
func getBundles(ref name.Reference, remoteOpts []remote.Option) ([]*bundle.Bundle, *v1.Hash, error) {
90+
desc, err := remote.Get(ref, remoteOpts...)
91+
if err != nil {
92+
return nil, nil, fmt.Errorf("error getting image descriptor: %w", err)
93+
}
94+
95+
digest := ref.Context().Digest(desc.Digest.String())
96+
97+
referrers, err := remote.Referrers(digest, remoteOpts...)
98+
if err != nil {
99+
return nil, nil, fmt.Errorf("error getting referrers: %w", err)
100+
}
101+
refManifest, err := referrers.IndexManifest()
102+
if err != nil {
103+
return nil, nil, fmt.Errorf("error getting referrers manifest: %w", err)
104+
}
105+
106+
bundles := make([]*bundle.Bundle, 0)
107+
108+
for _, refDesc := range refManifest.Manifests {
109+
if !strings.HasPrefix(refDesc.ArtifactType, "application/vnd.dev.sigstore.bundle") {
110+
continue
111+
}
112+
113+
refImg, err := remote.Image(ref.Context().Digest(refDesc.Digest.String()), remoteOpts...)
114+
if err != nil {
115+
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
116+
}
117+
layers, err := refImg.Layers()
118+
if err != nil {
119+
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
120+
}
121+
layer0, err := layers[0].Uncompressed()
122+
if err != nil {
123+
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
124+
}
125+
bundleBytes, err := io.ReadAll(layer0)
126+
if err != nil {
127+
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
128+
}
129+
b := &bundle.Bundle{}
130+
err = b.UnmarshalJSON(bundleBytes)
131+
if err != nil {
132+
return nil, nil, fmt.Errorf("error unmarshalling bundle: %w", err)
133+
}
134+
bundles = append(bundles, b)
135+
}
136+
if len(bundles) == 0 {
137+
return nil, nil, fmt.Errorf("no bundle found in referrers")
138+
}
139+
return bundles, &desc.Digest, nil
140+
}

pkg/webhook/clusterimagepolicy/clusterimagepolicy_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ type Authority struct {
8686
Attestations []AttestationPolicy `json:"attestations,omitempty"`
8787
// +optional
8888
RFC3161Timestamp *RFC3161Timestamp `json:"rfc3161timestamp,omitempty"`
89+
// +optional
90+
SignatureFormat string `json:"signatureFormat,omitempty"`
8991
}
9092

9193
// This references a public verification key stored in
@@ -325,6 +327,7 @@ func convertAuthorityV1Alpha1ToWebhook(in v1alpha1.Authority) *Authority {
325327
CTLog: in.CTLog,
326328
RFC3161Timestamp: rfc3161Timestamp,
327329
Attestations: attestations,
330+
SignatureFormat: in.SignatureFormat,
328331
}
329332
}
330333

pkg/webhook/validation.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@ import (
2424
"knative.dev/pkg/logging"
2525

2626
"github.com/sigstore/cosign/v2/pkg/cosign"
27-
"github.com/sigstore/cosign/v2/pkg/oci"
2827
"github.com/sigstore/sigstore/pkg/signature"
2928
)
3029

31-
func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, hashAlgo crypto.Hash, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) {
30+
func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, hashAlgo crypto.Hash, checkOpts *cosign.CheckOpts) ([]Signature, error) {
3231
if len(keys) == 0 {
3332
return validSignatures(ctx, ref, checkOpts)
3433
}
@@ -58,16 +57,24 @@ func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, has
5857
var cosignVerifySignatures = cosign.VerifyImageSignatures
5958
var cosignVerifyAttestations = cosign.VerifyImageAttestations
6059

61-
func validSignatures(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) {
60+
func validSignatures(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]Signature, error) {
6261
checkOpts.ClaimVerifier = cosign.SimpleClaimVerifier
6362
sigs, _, err := cosignVerifySignatures(ctx, ref, checkOpts)
64-
return sigs, err
63+
sigList := make([]Signature, len(sigs))
64+
for i, s := range sigs {
65+
sigList[i] = s
66+
}
67+
return sigList, err
6568
}
6669

67-
func validAttestations(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) {
70+
func validAttestations(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]Signature, error) {
6871
checkOpts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
6972
attestations, _, err := cosignVerifyAttestations(ctx, ref, checkOpts)
70-
return attestations, err
73+
sigList := make([]Signature, len(attestations))
74+
for i, s := range attestations {
75+
sigList[i] = s
76+
}
77+
return sigList, err
7178
}
7279

7380
func parsePems(b []byte) []*pem.Block {

0 commit comments

Comments
 (0)