Skip to content

Commit f76b4a4

Browse files
authored
feat(sign): option to generate sigstore bundle after signing (#943)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent 448d956 commit f76b4a4

File tree

9 files changed

+221
-36
lines changed

9 files changed

+221
-36
lines changed

app/cli/cmd/attestation_push.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
)
2727

2828
func newAttestationPushCmd() *cobra.Command {
29-
var pkPath string
29+
var pkPath, bundle string
3030
var annotationsFlag []string
3131
cmd := &cobra.Command{
3232
Use: "push",
@@ -57,7 +57,7 @@ func newAttestationPushCmd() *cobra.Command {
5757
return fmt.Errorf("getting executable information: %w", err)
5858
}
5959
a, err := action.NewAttestationPush(&action.AttestationPushOpts{
60-
ActionsOpts: actionOpts, KeyPath: pkPath, CLIVersion: info.Version, CLIDigest: info.Digest,
60+
ActionsOpts: actionOpts, KeyPath: pkPath, BundlePath: bundle, CLIVersion: info.Version, CLIDigest: info.Digest,
6161
})
6262
if err != nil {
6363
return fmt.Errorf("failed to load action: %w", err)
@@ -99,6 +99,7 @@ func newAttestationPushCmd() *cobra.Command {
9999

100100
cmd.Flags().StringVarP(&pkPath, "key", "k", "", "reference (path or env variable name) to the cosign or KMS key that will be used to sign the attestation")
101101
cmd.Flags().StringSliceVar(&annotationsFlag, "annotation", nil, "additional annotation in the format of key=value")
102+
cmd.Flags().StringVar(&bundle, "bundle", "", "output a Sigstore bundle to the provided path ")
102103
flagAttestationID(cmd)
103104

104105
return cmd

app/cli/internal/action/attestation_push.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232

3333
type AttestationPushOpts struct {
3434
*ActionsOpts
35-
KeyPath, CLIVersion, CLIDigest string
35+
KeyPath, CLIVersion, CLIDigest, BundlePath string
3636
}
3737

3838
type AttestationResult struct {
@@ -43,8 +43,8 @@ type AttestationResult struct {
4343

4444
type AttestationPush struct {
4545
*ActionsOpts
46-
c *crafter.Crafter
47-
keyPath, cliVersion, cliDigest string
46+
c *crafter.Crafter
47+
keyPath, cliVersion, cliDigest, bundlePath string
4848
}
4949

5050
func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) {
@@ -59,6 +59,7 @@ func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) {
5959
keyPath: cfg.KeyPath,
6060
cliVersion: cfg.CLIVersion,
6161
cliDigest: cfg.CLIDigest,
62+
bundlePath: cfg.BundlePath,
6263
}, nil
6364
}
6465

@@ -131,9 +132,9 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
131132
// Indicate that we are done with the attestation
132133
action.c.CraftingState.Attestation.FinishedAt = timestamppb.New(time.Now())
133134

134-
wrappedSigner := signer.GetSigner(action.keyPath, action.Logger, pb.NewSigningServiceClient(action.CPConnection))
135-
renderer, err := renderer.NewAttestationRenderer(action.c.CraftingState, action.cliVersion, action.cliDigest, wrappedSigner,
136-
renderer.WithLogger(action.Logger))
135+
sig := signer.GetSigner(action.keyPath, action.Logger, pb.NewSigningServiceClient(action.CPConnection))
136+
renderer, err := renderer.NewAttestationRenderer(action.c.CraftingState, action.cliVersion, action.cliDigest, sig,
137+
renderer.WithLogger(action.Logger), renderer.WithBundleOutputPath(action.bundlePath))
137138
if err != nil {
138139
return nil, err
139140
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ require (
8181
github.com/posthog/posthog-go v0.0.0-20240327112532-87b23fe11103
8282
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
8383
github.com/sigstore/fulcio v1.4.5
84+
github.com/sigstore/protobuf-specs v0.3.0
8485
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3
8586
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3
8687
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,8 @@ github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDF
12781278
github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y=
12791279
github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc=
12801280
github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8=
1281+
github.com/sigstore/protobuf-specs v0.3.0 h1:E49qS++llp4psM+3NNVEb+C4AD422bT9VkOQIPrNLpA=
1282+
github.com/sigstore/protobuf-specs v0.3.0/go.mod h1:ynKzXpqr3dUj2Xk9O/5ZUhjnpi0F53DNi5AdH6pS3jc=
12811283
github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8=
12821284
github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc=
12831285
github.com/sigstore/sigstore v1.8.3 h1:G7LVXqL+ekgYtYdksBks9B38dPoIsbscjQJX/MGWkA4=
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDtzCCAZ+gAwIBAgIURcRj3/yARibWiNcsJcKfhvrSNZQwDQYJKoZIhvcNAQEL
3+
BQAwXjELMAkGA1UEBhMCRVMxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAoM
4+
CUNoYWlubG9vcDESMBAGA1UECwwJY2hhaW5sb29wMRIwEAYDVQQDDAljaGFpbmxv
5+
b3AwHhcNMjQwNjA3MDkzMzQ5WhcNMjQwNjA3MDk0MzQ5WjAvMS0wKwYDVQQKEyRm
6+
YjcwMzU5ZC04ZGYxLTQyMDEtODg0Ny1hYzBkNTFhMWI4NTIwWTATBgcqhkjOPQIB
7+
BggqhkjOPQMBBwNCAATcwKEhDoE8m6u00qLYDv8M9SWwweRRLW1MYkDUFVDMyB/w
8+
SrUak9Vb7aSfiEBEx+QLP21WA18Pp5JQDoBpiQpFo2cwZTAOBgNVHQ8BAf8EBAMC
9+
B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFOzOtF3VZK//Lr58A5so
10+
DAcA1VchMB8GA1UdIwQYMBaAFKDk5Tdk8eOOMf/+GDw+62HNH6XKMA0GCSqGSIb3
11+
DQEBCwUAA4ICAQAifPG76QnxJWPHs9zXyPTmjNmIpmlgFdbkV4bCg1cG7l44xqZy
12+
fYW1OFo/WgAYqjSKlb6HbTh6uEoWk93dbnlPNPTSMbBE9fSqazXPp+o/5DKQT7uj
13+
wgQfTsNW48d4qKpGcAFunM9QesoX5SgNbiIdnW2xQOhjUDTxBmyallS4XBO/oGBF
14+
aETPjP7Syn+F05OaCW0ektiOHqvSnJ7BKrSfSohOBbUfT9c2KZ6v063XLMtQOJFL
15+
0kigcjwc/SwESImvN0a0RvPgZoantsjC3UQbzKzycmzXJAQV5JO/DvM9B0Je3dkv
16+
Wlp08jGBazAvRbBndjqR58JoQ5pncSycr+75q7aK7DjXoFch8eJDi1rJLE12FTvn
17+
W34cGx6EFBejEIAkqvPIxpOi37PG1SlCqkj4WfbB9A0ktml1S4P7RQm8ONY4CvP6
18+
8zQhM0PCCpK9F3XRFZCXZPBpqEhOEtVoxgogb9X1tCxqRpJmyYFNWgdG5Se6yfk4
19+
4xJ/G7lcG9jLOyFK+hXvz7pzs8PdJmlyb1vOHavnkSsd7L/ZDRntVRTncZEtKe3p
20+
0PbIvrCpD85M0GCv21e6mNmEjxvlxEW9wjq+oK5CFZAngd0Pl8LmT/5S5Gr6Pxft
21+
zXuZEUMZumYNPw2L5XZ3rkAE04p1KhxxEo6AQWNTdAkgiktmQ8cEdLe13g==
22+
-----END CERTIFICATE-----

internal/attestation/renderer/renderer.go

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,34 @@ package renderer
1717

1818
import (
1919
"bytes"
20+
"encoding/base64"
2021
"encoding/json"
22+
"encoding/pem"
2123
"errors"
2224
"fmt"
25+
"os"
2326

2427
v1 "github.com/chainloop-dev/chainloop/internal/attestation/crafter/api/attestation/v1"
2528
"github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop"
29+
chainloopsigner "github.com/chainloop-dev/chainloop/internal/attestation/signer/chainloop"
2630
intoto "github.com/in-toto/attestation/go/v1"
2731
"github.com/rs/zerolog"
2832
"github.com/secure-systems-lab/go-securesystemslib/dsse"
33+
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
34+
v12 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
35+
sigstoredsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse"
2936
sigstoresigner "github.com/sigstore/sigstore/pkg/signature"
37+
sigdsee "github.com/sigstore/sigstore/pkg/signature/dsse"
3038
"google.golang.org/protobuf/encoding/protojson"
3139
)
3240

3341
type AttestationRenderer struct {
34-
logger zerolog.Logger
35-
att *v1.Attestation
36-
renderer r
37-
signer sigstoresigner.Signer
42+
logger zerolog.Logger
43+
att *v1.Attestation
44+
renderer r
45+
signer sigstoresigner.Signer
46+
dsseSigner sigstoresigner.Signer
47+
bundlePath string
3848
}
3949

4050
type r interface {
@@ -49,16 +59,23 @@ func WithLogger(logger zerolog.Logger) Opt {
4959
}
5060
}
5161

62+
func WithBundleOutputPath(bundlePath string) Opt {
63+
return func(ar *AttestationRenderer) {
64+
ar.bundlePath = bundlePath
65+
}
66+
}
67+
5268
func NewAttestationRenderer(state *v1.CraftingState, builderVersion, builderDigest string, signer sigstoresigner.Signer, opts ...Opt) (*AttestationRenderer, error) {
5369
if state.GetAttestation() == nil {
5470
return nil, errors.New("attestation not initialized")
5571
}
5672

5773
r := &AttestationRenderer{
58-
logger: zerolog.Nop(),
59-
att: state.GetAttestation(),
60-
signer: signer,
61-
renderer: chainloop.NewChainloopRendererV02(state.GetAttestation(), builderVersion, builderDigest),
74+
logger: zerolog.Nop(),
75+
att: state.GetAttestation(),
76+
dsseSigner: sigdsee.WrapSigner(signer, "application/vnd.in-toto+json"),
77+
signer: signer,
78+
renderer: chainloop.NewChainloopRendererV02(state.GetAttestation(), builderVersion, builderDigest),
6279
}
6380

6481
for _, opt := range opts {
@@ -87,15 +104,77 @@ func (ab *AttestationRenderer) Render() (*dsse.Envelope, error) {
87104
return nil, err
88105
}
89106

90-
signedAtt, err := ab.signer.SignMessage(bytes.NewReader(rawStatement))
107+
signedAtt, err := ab.dsseSigner.SignMessage(bytes.NewReader(rawStatement))
91108
if err != nil {
92109
return nil, fmt.Errorf("signing message: %w", err)
93110
}
94111

95-
var dseeEnvelope dsse.Envelope
96-
if err := json.Unmarshal(signedAtt, &dseeEnvelope); err != nil {
112+
var dsseEnvelope dsse.Envelope
113+
if err := json.Unmarshal(signedAtt, &dsseEnvelope); err != nil {
97114
return nil, err
98115
}
99116

100-
return &dseeEnvelope, nil
117+
if ab.bundlePath != "" {
118+
// Create sigstore bundle for the contents of this attestation
119+
bundle, err := ab.envelopeToBundle(dsseEnvelope)
120+
if err != nil {
121+
return nil, fmt.Errorf("loading bundle: %w", err)
122+
}
123+
json, err := protojson.Marshal(bundle)
124+
if err != nil {
125+
return nil, fmt.Errorf("marshalling bundle: %w", err)
126+
}
127+
ab.logger.Info().Msg(fmt.Sprintf("generating Sigstore bundle %s", ab.bundlePath))
128+
err = os.WriteFile(ab.bundlePath, json, 0600)
129+
if err != nil {
130+
return nil, fmt.Errorf("writing bundle: %w", err)
131+
}
132+
}
133+
134+
return &dsseEnvelope, nil
135+
}
136+
137+
func (ab *AttestationRenderer) envelopeToBundle(dsseEnvelope dsse.Envelope) (*protobundle.Bundle, error) {
138+
// DSSE Envelope is already base64 encoded, we need to decode to prevent it from being encoded twice
139+
payload, err := base64.StdEncoding.DecodeString(dsseEnvelope.Payload)
140+
if err != nil {
141+
return nil, fmt.Errorf("decoding: %w", err)
142+
}
143+
bundle := &protobundle.Bundle{
144+
MediaType: "application/vnd.dev.sigstore.bundle+json;version=0.3",
145+
Content: &protobundle.Bundle_DsseEnvelope{DsseEnvelope: &sigstoredsse.Envelope{
146+
Payload: payload,
147+
PayloadType: dsseEnvelope.PayloadType,
148+
Signatures: []*sigstoredsse.Signature{
149+
{
150+
Sig: []byte(dsseEnvelope.Signatures[0].Sig),
151+
Keyid: dsseEnvelope.Signatures[0].KeyID,
152+
},
153+
},
154+
}},
155+
VerificationMaterial: &protobundle.VerificationMaterial{},
156+
}
157+
158+
// extract verification materials
159+
// Note: we don't support PublicKey materials (from cosign.key and KMS signers), since Chainloop doesn't (yet) store
160+
// public keys.
161+
if v, ok := ab.signer.(*chainloopsigner.Signer); ok {
162+
chain := v.Chain
163+
certs := make([]*v12.X509Certificate, 0)
164+
// Store cert chain except root certificate, as it's required to be provided separately
165+
for _, c := range chain[0 : len(chain)-1] {
166+
block, _ := pem.Decode([]byte(c))
167+
if block == nil {
168+
return nil, fmt.Errorf("failed to decode PEM block")
169+
}
170+
certs = append(certs, &v12.X509Certificate{RawBytes: block.Bytes})
171+
}
172+
bundle.VerificationMaterial.Content = &protobundle.VerificationMaterial_X509CertificateChain{
173+
X509CertificateChain: &v12.X509CertificateChain{
174+
Certificates: certs,
175+
},
176+
}
177+
}
178+
179+
return bundle, nil
101180
}

0 commit comments

Comments
 (0)