Skip to content
This repository was archived by the owner on Sep 23, 2025. It is now read-only.

Commit 2f7018e

Browse files
codysoylandmalancas
andcommitted
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 <[email protected]> * Grab image digest for attestation step Signed-off-by: Meredith Lancaster <[email protected]> * comment Signed-off-by: Meredith Lancaster <[email protected]> * update workflow name Signed-off-by: Meredith Lancaster <[email protected]> * add release directions Signed-off-by: Meredith Lancaster <[email protected]> * undo ko config changes Signed-off-by: Meredith Lancaster <[email protected]> * add fork specific options to ko build call Signed-off-by: Meredith Lancaster <[email protected]> * Change version format --------- Signed-off-by: Meredith Lancaster <[email protected]> Co-authored-by: Cody Soyland <[email protected]> * set release as target branch (#161) Signed-off-by: Meredith Lancaster <[email protected]> * Add support for Sigstore Bundles using sigstore-go verifier Signed-off-by: Cody Soyland <[email protected]> * Update docs Signed-off-by: Cody Soyland <[email protected]> * Rename func Signed-off-by: Cody Soyland <[email protected]> * Comment on observe timestamp setting Signed-off-by: Cody Soyland <[email protected]> * Refactor trusted material, add support for default TUF repo in bundle verifier Signed-off-by: Cody Soyland <[email protected]> * Remove accidental code Signed-off-by: Cody Soyland <[email protected]> * Fix tlog verification options Signed-off-by: Cody Soyland <[email protected]> --------- Signed-off-by: Meredith Lancaster <[email protected]> Signed-off-by: Cody Soyland <[email protected]> Co-authored-by: Meredith Lancaster <[email protected]>
1 parent 6beed9d commit 2f7018e

File tree

16 files changed

+729
-14
lines changed

16 files changed

+729
-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: 2 additions & 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.4
6565
github.com/sigstore/protobuf-specs v0.3.2
6666
github.com/sigstore/scaffolding v0.7.11
67+
github.com/sigstore/sigstore-go v0.6.2
6768
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.10
6869
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.10
6970
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.10
@@ -193,6 +194,7 @@ require (
193194
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
194195
github.com/hashicorp/vault/api v1.15.0 // indirect
195196
github.com/imdario/mergo v0.3.16 // indirect
197+
github.com/in-toto/attestation v1.1.0 // indirect
196198
github.com/in-toto/in-toto-golang v0.9.0 // indirect
197199
github.com/inconshreveable/mousetrap v1.1.0 // indirect
198200
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect
@@ -231,7 +233,6 @@ require (
231233
github.com/sassoftware/relic v7.2.1+incompatible // indirect
232234
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect
233235
github.com/shibumi/go-pathspec v1.3.0 // indirect
234-
github.com/sigstore/sigstore-go v0.6.2 // indirect
235236
github.com/sigstore/timestamp-authority v1.2.2 // indirect
236237
github.com/sirupsen/logrus v1.9.3 // indirect
237238
github.com/sourcegraph/conc v0.3.0 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg=
384384
github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM=
385385
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
386386
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
387+
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
388+
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
387389
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
388390
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
389391
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
@@ -534,6 +536,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
534536
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
535537
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
536538
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
539+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
540+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
537541
github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM=
538542
github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
539543
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
@@ -747,6 +751,8 @@ github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbm
747751
github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU=
748752
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
749753
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
754+
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
755+
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
750756
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
751757
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
752758
github.com/sigstore/cosign/v2 v2.4.1 h1:b8UXEfJFks3hmTwyxrRNrn6racpmccUycBHxDMkEPvU=

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.GetCertificate()
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

0 commit comments

Comments
 (0)