Skip to content

Commit f678eda

Browse files
authored
Merge pull request #3269 from st3penta/EC-1700
Add bundle auto-detection to Rego sigstore builtins
2 parents a7e9985 + e5e09a6 commit f678eda

12 files changed

Lines changed: 1872 additions & 60 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package sigstore_attestation_only
2+
3+
import rego.v1
4+
5+
# METADATA
6+
# custom:
7+
# short_name: valid
8+
deny contains result if {
9+
some error in _errors
10+
result := {"code": "sigstore_attestation_only.valid", "msg": error}
11+
}
12+
13+
_errors contains error if {
14+
not _image_ref
15+
error := "input.image.ref not set"
16+
}
17+
18+
_errors contains error if {
19+
not _sigstore_opts
20+
error := "default sigstore options not set"
21+
}
22+
23+
_errors contains error if {
24+
info := ec.sigstore.verify_attestation(_image_ref, _sigstore_opts)
25+
some raw_error in info.errors
26+
error := sprintf("image attestation verification failed: %s", [raw_error])
27+
}
28+
29+
_errors contains error if {
30+
info := ec.sigstore.verify_attestation(_image_ref, _sigstore_opts)
31+
count(info.attestations) == 0
32+
error := "verification successful, but no attestations found"
33+
}
34+
35+
_errors contains error if {
36+
info := ec.sigstore.verify_attestation(_image_ref, _sigstore_opts)
37+
some att in info.attestations
38+
count(att.signatures) == 0
39+
error := sprintf("attestation has no signatures: %s", [att])
40+
}
41+
42+
_errors contains error if {
43+
info := ec.sigstore.verify_attestation(_image_ref, _sigstore_opts)
44+
some att in info.attestations
45+
46+
not _is_supported_slsa_predicate(att.statement.predicateType)
47+
error := sprintf("unexpected statement predicate: %s", [att.statement.predicateType])
48+
}
49+
50+
_errors contains error if {
51+
info := ec.sigstore.verify_attestation(_image_ref, _sigstore_opts)
52+
some att in info.attestations
53+
builder_id := _builder_id(att)
54+
builder_id != "https://tekton.dev/chains/v2"
55+
error := sprintf("unexpected builder ID: %s", [builder_id])
56+
}
57+
58+
_image_ref := input.image.ref
59+
60+
_sigstore_opts := data.config.default_sigstore_opts
61+
62+
_builder_id(att) := value if {
63+
value := att.statement.predicate.builder.id
64+
} else := value if {
65+
value := att.statement.predicate.runDetails.builder.id
66+
} else := "MISSING"
67+
68+
_is_supported_slsa_predicate(predicate_type) if {
69+
predicate_type == "https://slsa.dev/provenance/v0.2"
70+
}
71+
72+
_is_supported_slsa_predicate(predicate_type) if {
73+
predicate_type == "https://slsa.dev/provenance/v1"
74+
}

acceptance/image/image.go

Lines changed: 148 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import (
5454
"github.com/sigstore/cosign/v3/pkg/oci/static"
5555
cosigntypes "github.com/sigstore/cosign/v3/pkg/types"
5656
rc "github.com/sigstore/rekor/pkg/client"
57+
"github.com/sigstore/rekor/pkg/generated/models"
5758
"github.com/sigstore/sigstore/pkg/cryptoutils"
5859
"github.com/sigstore/sigstore/pkg/signature"
5960
"gopkg.in/go-jose/go-jose.v2/json"
@@ -208,7 +209,7 @@ func createSignatureData(ctx context.Context, imageName string, digestImage name
208209
}
209210

210211
// Upload to transparency log to get bundle information like Tekton Chains does
211-
rekorBundle, err := uploadToTransparencyLog(ctx, payload, rawSignature, signer)
212+
rekorBundle, _, err := uploadToTransparencyLog(ctx, payload, rawSignature, signer)
212213
if err != nil {
213214
return nil, err
214215
}
@@ -300,42 +301,43 @@ func CreateAndPushImageSignature(ctx context.Context, imageName string, keyName
300301
}
301302

302303
// uploadToTransparencyLog uploads a signature to the transparency log and returns the bundle
303-
func uploadToTransparencyLog(ctx context.Context, payload []byte, rawSignature []byte, signer signature.SignerVerifier) (*bundle.RekorBundle, error) {
304+
// along with the raw tlog entry (needed for creating protobuf bundles).
305+
func uploadToTransparencyLog(ctx context.Context, payload []byte, rawSignature []byte, signer signature.SignerVerifier) (*bundle.RekorBundle, *models.LogEntryAnon, error) {
304306
// Get public key or cert for transparency log upload
305307
pkoc, err := getPublicKeyOrCert(signer)
306308
if err != nil {
307-
return nil, fmt.Errorf("failed to get public key or cert: %w", err)
309+
return nil, nil, fmt.Errorf("failed to get public key or cert: %w", err)
308310
}
309311

310312
// Get Rekor URL
311313
rekorURL, err := rekor.StubRekor(ctx)
312314
if err != nil {
313-
return nil, fmt.Errorf("failed to get stub rekor URL: %w", err)
315+
return nil, nil, fmt.Errorf("failed to get stub rekor URL: %w", err)
314316
}
315317

316318
rekorClient, err := rc.GetRekorClient(rekorURL)
317319
if err != nil {
318-
return nil, fmt.Errorf("failed to get rekor client: %w", err)
320+
return nil, nil, fmt.Errorf("failed to get rekor client: %w", err)
319321
}
320322

321323
// Compute payload checksum
322324
checksum := sha256.New()
323325
if _, err := checksum.Write(payload); err != nil {
324-
return nil, fmt.Errorf("error checksuming payload: %w", err)
326+
return nil, nil, fmt.Errorf("error checksuming payload: %w", err)
325327
}
326328

327329
tlogEntry, err := cosign.TLogUpload(ctx, rekorClient, rawSignature, checksum, pkoc)
328330
if err != nil {
329-
return nil, fmt.Errorf("failed to upload to transparency log: %w", err)
331+
return nil, nil, fmt.Errorf("failed to upload to transparency log: %w", err)
330332
}
331333

332334
// Create bundle from the actual transparency log entry
333335
rekorBundle := bundle.EntryToBundle(tlogEntry)
334336
if rekorBundle == nil {
335-
return nil, fmt.Errorf("rekorBundle is nil after EntryToBundle")
337+
return nil, nil, fmt.Errorf("rekorBundle is nil after EntryToBundle")
336338
}
337339

338-
return rekorBundle, nil
340+
return rekorBundle, tlogEntry, nil
339341
}
340342

341343
// getImageDigestAndRef returns the image, its digest, and digest reference for signing
@@ -533,7 +535,7 @@ func createAndPushAttestationInternal(ctx context.Context, imageName, keyName st
533535
}
534536

535537
// Upload to transparency log to get bundle information like Tekton Chains does
536-
rekorBundle, err := uploadToTransparencyLog(ctx, signedAttestation, rawSignature, signer)
538+
rekorBundle, _, err := uploadToTransparencyLog(ctx, signedAttestation, rawSignature, signer)
537539
if err != nil {
538540
return ctx, err
539541
}
@@ -670,8 +672,8 @@ func CreateAndPushAttestationReferrer(ctx context.Context, imageName, keyName st
670672
return ctx, fmt.Errorf("error stubbing rekor endpoints for attestation: %w", err)
671673
}
672674

673-
// Upload to transparency log to get bundle information
674-
rekorBundle, err := uploadToTransparencyLog(ctx, signedAttestation, rawSignature, signer)
675+
// Upload to transparency log to get bundle information like Tekton Chains does
676+
rekorBundle, _, err := uploadToTransparencyLog(ctx, signedAttestation, rawSignature, signer)
675677
if err != nil {
676678
return ctx, err
677679
}
@@ -717,6 +719,111 @@ func CreateAndPushAttestationReferrer(ctx context.Context, imageName, keyName st
717719
return ctx, nil
718720
}
719721

722+
// CreateAndPushBundleAttestationReferrer creates a protobuf Sigstore bundle attestation
723+
// and pushes it as an OCI referrer with the bundle media type that cosign recognizes.
724+
func CreateAndPushBundleAttestationReferrer(ctx context.Context, imageName, keyName string) (context.Context, error) {
725+
var state *imageState
726+
ctx, err := testenv.SetupState(ctx, &state)
727+
if err != nil {
728+
return ctx, err
729+
}
730+
731+
if state.ReferrerAttestations[imageName] != "" {
732+
return ctx, nil
733+
}
734+
735+
image, digest, _, err := getImageDigestAndRef(ctx, imageName)
736+
if err != nil {
737+
return ctx, err
738+
}
739+
740+
statement, err := attestation.CreateStatementFor(imageName, image)
741+
if err != nil {
742+
return ctx, err
743+
}
744+
745+
signedAttestation, err := attestation.SignStatement(ctx, keyName, statement)
746+
if err != nil {
747+
return ctx, err
748+
}
749+
750+
var sig *cosign.Signatures
751+
sig, err = unmarshallSignatures(signedAttestation)
752+
if err != nil {
753+
return ctx, err
754+
}
755+
if sig == nil {
756+
return ctx, fmt.Errorf("failed to extract signature from attestation: no signatures found")
757+
}
758+
759+
state.ReferrerAttestationSignatures[imageName] = Signature{
760+
KeyID: sig.KeyID,
761+
Signature: sig.Sig,
762+
}
763+
764+
var rawSignature []byte
765+
if sig.Sig != "" {
766+
rawSignature, err = base64.StdEncoding.DecodeString(sig.Sig)
767+
if err != nil {
768+
return ctx, fmt.Errorf("failed to decode signature: %w", err)
769+
}
770+
}
771+
772+
signer, err := crypto.SignerWithKey(ctx, keyName)
773+
if err != nil {
774+
return ctx, err
775+
}
776+
777+
publicKey, err := signer.PublicKey()
778+
if err != nil {
779+
return ctx, fmt.Errorf("failed to get public key: %w", err)
780+
}
781+
782+
publicKeyBytes, err := cryptoutils.MarshalPublicKeyToPEM(publicKey)
783+
if err != nil {
784+
return ctx, fmt.Errorf("failed to marshal public key: %w", err)
785+
}
786+
787+
err = rekor.StubRekorEntryCreationForAttestation(ctx, signedAttestation, publicKeyBytes)
788+
if err != nil {
789+
return ctx, fmt.Errorf("error stubbing rekor endpoints for attestation: %w", err)
790+
}
791+
792+
_, tlogEntry, err := uploadToTransparencyLog(ctx, signedAttestation, rawSignature, signer)
793+
if err != nil {
794+
return ctx, err
795+
}
796+
797+
statementPayload, err := json.Marshal(statement)
798+
if err != nil {
799+
return ctx, fmt.Errorf("failed to marshal statement: %w", err)
800+
}
801+
802+
bundleBytes, err := bundle.MakeNewBundle(publicKey, tlogEntry, statementPayload, signedAttestation, publicKeyBytes, nil)
803+
if err != nil {
804+
return ctx, fmt.Errorf("failed to create protobuf bundle: %w", err)
805+
}
806+
807+
digestRef, err := getDigestRefForImage(ctx, imageName, digest)
808+
if err != nil {
809+
return ctx, err
810+
}
811+
812+
err = cosignRemote.WriteAttestationNewBundleFormat(
813+
digestRef,
814+
bundleBytes,
815+
statement.PredicateType,
816+
cosignRemote.WithRemoteOptions(remote.WithContext(ctx)),
817+
)
818+
if err != nil {
819+
return ctx, fmt.Errorf("failed to write attestation bundle referrer: %w", err)
820+
}
821+
822+
state.ReferrerAttestations[imageName] = digestRef.String()
823+
824+
return ctx, nil
825+
}
826+
720827
// CreateAndPushV1Attestation for a named image creates a SLSA v1.0 attestation
721828
// and pushes it to the stub registry
722829
func CreateAndPushV1Attestation(ctx context.Context, imageName, keyName string) (context.Context, error) {
@@ -1197,12 +1304,15 @@ func AttestationSignaturesFrom(ctx context.Context, prefix string) (map[string]s
11971304
state := testenv.FetchState[imageState](ctx)
11981305

11991306
signatures := map[string]string{}
1200-
for name, signature := range state.AttestationSignatures {
1201-
if signature.KeyID != "" {
1202-
signatures[fmt.Sprintf("%s_KEY_ID_%s", prefix, name)] = signature.KeyID
1203-
}
1204-
if signature.Signature != "" {
1205-
signatures[fmt.Sprintf("%s_%s", prefix, name)] = signature.Signature
1307+
// Referrer entries first so legacy entries take precedence (overwrite) for same image name
1308+
for _, m := range []map[string]Signature{state.ReferrerAttestationSignatures, state.AttestationSignatures} {
1309+
for name, signature := range m {
1310+
if signature.KeyID != "" {
1311+
signatures[fmt.Sprintf("%s_KEY_ID_%s", prefix, name)] = signature.KeyID
1312+
}
1313+
if signature.Signature != "" {
1314+
signatures[fmt.Sprintf("%s_%s", prefix, name)] = signature.Signature
1315+
}
12061316
}
12071317
}
12081318

@@ -1217,8 +1327,11 @@ func RawAttestationSignaturesFrom(ctx context.Context) map[string]string {
12171327
state := testenv.FetchState[imageState](ctx)
12181328

12191329
ret := map[string]string{}
1220-
for ref, signature := range state.AttestationSignatures {
1221-
ret[fmt.Sprintf("ATTESTATION_SIGNATURE_%s", ref)] = signature.Signature
1330+
// Referrer entries first so legacy entries take precedence (overwrite) for same image name
1331+
for _, m := range []map[string]Signature{state.ReferrerAttestationSignatures, state.AttestationSignatures} {
1332+
for ref, signature := range m {
1333+
ret[fmt.Sprintf("ATTESTATION_SIGNATURE_%s", ref)] = signature.Signature
1334+
}
12221335
}
12231336

12241337
return ret
@@ -1232,12 +1345,15 @@ func ImageSignaturesFrom(ctx context.Context, prefix string) (map[string]string,
12321345
state := testenv.FetchState[imageState](ctx)
12331346

12341347
ret := map[string]string{}
1235-
for name, signature := range state.ImageSignatures {
1236-
if signature.KeyID != "" {
1237-
ret[fmt.Sprintf("%s_KEY_ID_%s", prefix, name)] = signature.KeyID
1238-
}
1239-
if signature.Signature != "" {
1240-
ret[fmt.Sprintf("%s_%s", prefix, name)] = signature.Signature
1348+
// Referrer entries first so legacy entries take precedence (overwrite) for same image name
1349+
for _, m := range []map[string]Signature{state.ReferrerImageSignatures, state.ImageSignatures} {
1350+
for name, signature := range m {
1351+
if signature.KeyID != "" {
1352+
ret[fmt.Sprintf("%s_KEY_ID_%s", prefix, name)] = signature.KeyID
1353+
}
1354+
if signature.Signature != "" {
1355+
ret[fmt.Sprintf("%s_%s", prefix, name)] = signature.Signature
1356+
}
12411357
}
12421358
}
12431359

@@ -1252,8 +1368,11 @@ func RawImageSignaturesFrom(ctx context.Context) map[string]string {
12521368
state := testenv.FetchState[imageState](ctx)
12531369

12541370
ret := map[string]string{}
1255-
for ref, signature := range state.ImageSignatures {
1256-
ret[fmt.Sprintf("IMAGE_SIGNATURE_%s", ref)] = signature.Signature
1371+
// Referrer entries first so legacy entries take precedence (overwrite) for same image name
1372+
for _, m := range []map[string]Signature{state.ReferrerImageSignatures, state.ImageSignatures} {
1373+
for ref, signature := range m {
1374+
ret[fmt.Sprintf("IMAGE_SIGNATURE_%s", ref)] = signature.Signature
1375+
}
12571376
}
12581377

12591378
return ret
@@ -1412,4 +1531,5 @@ func AddStepsTo(sc *godog.ScenarioContext) {
14121531
sc.Step(`^an OCI blob with content "([^"]*)" in the repo "([^"]*)"$`, createAndPushLayer)
14131532
sc.Step(`^a valid image signature referrer of "([^"]*)" image signed by the "([^"]*)" key$`, CreateAndPushImageSignatureReferrer)
14141533
sc.Step(`^a valid attestation referrer of "([^"]*)" signed by the "([^"]*)" key$`, CreateAndPushAttestationReferrer)
1534+
sc.Step(`^a valid bundle-format attestation referrer of "([^"]*)" signed by the "([^"]*)" key$`, CreateAndPushBundleAttestationReferrer)
14151535
}

0 commit comments

Comments
 (0)