Skip to content

Commit d6b9001

Browse files
authored
Loosen verification predicate type + better error messages (#2737)
* test policy-controller only. Signed-off-by: Ville Aikas <[email protected]> * Better error messages. Signed-off-by: Ville Aikas <[email protected]> --------- Signed-off-by: Ville Aikas <[email protected]>
1 parent f9d8e5d commit d6b9001

File tree

5 files changed

+57
-35
lines changed

5 files changed

+57
-35
lines changed

cmd/cosign/cli/verify/verify_attestation.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"os"
2424
"path/filepath"
25+
"strings"
2526

2627
"github.com/google/go-containerregistry/pkg/name"
2728
"github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio"
@@ -269,11 +270,16 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
269270

270271
var checked []oci.Signature
271272
var validationErrors []error
273+
// To aid in determining if there's a mismatch in what predicateType
274+
// we're looking for and what we checked, keep track of them here so
275+
// that we can help the user figure out if there's a typo, etc.
276+
checkedPredicateTypes := []string{}
272277
for _, vp := range verified {
273-
payload, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, vp)
278+
payload, gotPredicateType, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, vp)
274279
if err != nil {
275280
return fmt.Errorf("converting to consumable policy validation: %w", err)
276281
}
282+
checkedPredicateTypes = append(checkedPredicateTypes, gotPredicateType)
277283
if len(payload) == 0 {
278284
// This is not the predicate type we're looking for.
279285
continue
@@ -309,7 +315,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
309315
}
310316

311317
if len(checked) == 0 {
312-
return fmt.Errorf("none of the attestations matched the predicate type: %s", c.PredicateType)
318+
return fmt.Errorf("none of the attestations matched the predicate type: %s, found: %s", c.PredicateType, strings.Join(checkedPredicateTypes, ","))
313319
}
314320

315321
// TODO: add CUE validation report to `PrintVerificationHeader`.

cmd/cosign/cli/verify/verify_blob_attestation.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
340340

341341
// This checks the predicate type -- if no error is returned and no payload is, then
342342
// the attestation is not of the given predicate type.
343-
if b, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, signature); b == nil && err == nil {
344-
return fmt.Errorf("invalid predicate type, expected %s", c.PredicateType)
343+
if b, gotPredicateType, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, signature); b == nil && err == nil {
344+
return fmt.Errorf("invalid predicate type, expected %s got %s", c.PredicateType, gotPredicateType)
345345
}
346346

347347
fmt.Fprintln(os.Stderr, "Verified OK")

cmd/cosign/cli/verify/verify_blob_attestation_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ func TestVerifyBlobAttestation(t *testing.T) {
8686
predicateType: "slsaprovenance",
8787
signature: dssePredicateMultipleSubjects,
8888
blobPath: blobPath,
89+
}, {
90+
description: "dsse envelope has multiple subjects, one is valid, but we are looking for different predicatetype",
91+
predicateType: "notreallyslsaprovenance",
92+
signature: dssePredicateMultipleSubjects,
93+
blobPath: blobPath,
94+
shouldErr: true,
8995
}, {
9096
description: "dsse envelope has multiple subjects, none has correct sha256 digest",
9197
predicateType: "slsaprovenance",

pkg/policy/attestation.go

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"context"
2020
"encoding/base64"
2121
"encoding/json"
22+
"errors"
2223
"fmt"
2324

2425
"github.com/in-toto/in-toto-golang/in_toto"
@@ -37,44 +38,52 @@ import (
3738
//
3839
// If there's no error, and payload is empty means the predicateType did not
3940
// match the attestation.
40-
func AttestationToPayloadJSON(ctx context.Context, predicateType string, verifiedAttestation oci.Signature) ([]byte, error) {
41-
// Check the predicate up front, no point in wasting time if it's invalid.
42-
predicateURI, err := options.ParsePredicateType(predicateType)
43-
44-
if err != nil {
45-
return nil, fmt.Errorf("invalid predicate type: %s", predicateType)
41+
// Returns the attestation type (PredicateType) if the payload was decoded
42+
// before the error happened, or in the case the predicateType that was
43+
// requested does not match. This is useful for callers to be able to provide
44+
// better error messages. For example, if there's a typo in the predicateType,
45+
// or the predicateType is not the one they are looking for. Without returning
46+
// this, it's hard for users to know which attestations/predicateTypes were
47+
// inspected.
48+
func AttestationToPayloadJSON(ctx context.Context, predicateType string, verifiedAttestation oci.Signature) ([]byte, string, error) {
49+
if predicateType == "" {
50+
return nil, "", errors.New("missing predicate type")
51+
}
52+
predicateURI, ok := options.PredicateTypeMap[predicateType]
53+
if !ok {
54+
// Not a custom one, use it as is.
55+
predicateURI = predicateType
4656
}
47-
4857
var payloadData map[string]interface{}
4958

5059
p, err := verifiedAttestation.Payload()
5160
if err != nil {
52-
return nil, fmt.Errorf("getting payload: %w", err)
61+
return nil, "", fmt.Errorf("getting payload: %w", err)
5362
}
5463

5564
err = json.Unmarshal(p, &payloadData)
5665
if err != nil {
57-
return nil, fmt.Errorf("unmarshaling payload data")
66+
return nil, "", fmt.Errorf("unmarshaling payload data")
5867
}
5968

6069
var decodedPayload []byte
6170
if val, ok := payloadData["payload"]; ok {
6271
decodedPayload, err = base64.StdEncoding.DecodeString(val.(string))
6372
if err != nil {
64-
return nil, fmt.Errorf("decoding payload: %w", err)
73+
return nil, "", fmt.Errorf("decoding payload: %w", err)
6574
}
6675
} else {
67-
return nil, fmt.Errorf("could not find payload in payload data")
76+
return nil, "", fmt.Errorf("could not find payload in payload data")
6877
}
6978

7079
// Only apply the policy against the requested predicate type
7180
var statement in_toto.Statement
7281
if err := json.Unmarshal(decodedPayload, &statement); err != nil {
73-
return nil, fmt.Errorf("unmarshal in-toto statement: %w", err)
82+
return nil, "", fmt.Errorf("unmarshal in-toto statement: %w", err)
7483
}
7584
if statement.PredicateType != predicateURI {
7685
// This is not the predicate we're looking for, so skip it.
77-
return nil, nil
86+
return nil, statement.PredicateType, nil
7887
}
7988

8089
// NB: In many (all?) of these cases, we could just return the
@@ -85,59 +94,59 @@ func AttestationToPayloadJSON(ctx context.Context, predicateType string, verifie
8594
case options.PredicateCustom:
8695
payload, err = json.Marshal(statement)
8796
if err != nil {
88-
return nil, fmt.Errorf("generating CosignStatement: %w", err)
97+
return nil, statement.PredicateType, fmt.Errorf("generating CosignStatement: %w", err)
8998
}
9099
case options.PredicateLink:
91100
var linkStatement in_toto.LinkStatement
92101
if err := json.Unmarshal(decodedPayload, &linkStatement); err != nil {
93-
return nil, fmt.Errorf("unmarshaling LinkStatement: %w", err)
102+
return nil, statement.PredicateType, fmt.Errorf("unmarshaling LinkStatement: %w", err)
94103
}
95104
payload, err = json.Marshal(linkStatement)
96105
if err != nil {
97-
return nil, fmt.Errorf("marshaling LinkStatement: %w", err)
106+
return nil, statement.PredicateType, fmt.Errorf("marshaling LinkStatement: %w", err)
98107
}
99108
case options.PredicateSLSA:
100109
var slsaProvenanceStatement in_toto.ProvenanceStatement
101110
if err := json.Unmarshal(decodedPayload, &slsaProvenanceStatement); err != nil {
102-
return nil, fmt.Errorf("unmarshaling ProvenanceStatement): %w", err)
111+
return nil, statement.PredicateType, fmt.Errorf("unmarshaling ProvenanceStatement): %w", err)
103112
}
104113
payload, err = json.Marshal(slsaProvenanceStatement)
105114
if err != nil {
106-
return nil, fmt.Errorf("marshaling ProvenanceStatement: %w", err)
115+
return nil, statement.PredicateType, fmt.Errorf("marshaling ProvenanceStatement: %w", err)
107116
}
108117
case options.PredicateSPDX, options.PredicateSPDXJSON:
109118
var spdxStatement in_toto.SPDXStatement
110119
if err := json.Unmarshal(decodedPayload, &spdxStatement); err != nil {
111-
return nil, fmt.Errorf("unmarshaling SPDXStatement: %w", err)
120+
return nil, statement.PredicateType, fmt.Errorf("unmarshaling SPDXStatement: %w", err)
112121
}
113122
payload, err = json.Marshal(spdxStatement)
114123
if err != nil {
115-
return nil, fmt.Errorf("marshaling SPDXStatement: %w", err)
124+
return nil, statement.PredicateType, fmt.Errorf("marshaling SPDXStatement: %w", err)
116125
}
117126
case options.PredicateCycloneDX:
118127
var cyclonedxStatement in_toto.CycloneDXStatement
119128
if err := json.Unmarshal(decodedPayload, &cyclonedxStatement); err != nil {
120-
return nil, fmt.Errorf("unmarshaling CycloneDXStatement: %w", err)
129+
return nil, statement.PredicateType, fmt.Errorf("unmarshaling CycloneDXStatement: %w", err)
121130
}
122131
payload, err = json.Marshal(cyclonedxStatement)
123132
if err != nil {
124-
return nil, fmt.Errorf("marshaling CycloneDXStatement: %w", err)
133+
return nil, statement.PredicateType, fmt.Errorf("marshaling CycloneDXStatement: %w", err)
125134
}
126135
case options.PredicateVuln:
127136
var vulnStatement attestation.CosignVulnStatement
128137
if err := json.Unmarshal(decodedPayload, &vulnStatement); err != nil {
129-
return nil, fmt.Errorf("unmarshaling CosignVulnStatement: %w", err)
138+
return nil, statement.PredicateType, fmt.Errorf("unmarshaling CosignVulnStatement: %w", err)
130139
}
131140
payload, err = json.Marshal(vulnStatement)
132141
if err != nil {
133-
return nil, fmt.Errorf("marshaling CosignVulnStatement: %w", err)
142+
return nil, statement.PredicateType, fmt.Errorf("marshaling CosignVulnStatement: %w", err)
134143
}
135144
default:
136145
// Valid URI type reaches here.
137146
payload, err = json.Marshal(statement)
138147
if err != nil {
139-
return nil, fmt.Errorf("generating Statement: %w", err)
148+
return nil, statement.PredicateType, fmt.Errorf("generating Statement: %w", err)
140149
}
141150
}
142-
return payload, nil
151+
return payload, statement.PredicateType, nil
143152
}

pkg/policy/attestation_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ func TestFailures(t *testing.T) {
103103
payload string
104104
predicateType string
105105
wantErrSubstring string
106-
}{{payload: "", predicateType: "notvalidpredicate", wantErrSubstring: "invalid predicate type"},
107-
{payload: "", wantErrSubstring: "unmarshaling payload data"}, {payload: "{badness", wantErrSubstring: "unmarshaling payload data"},
106+
}{{payload: "", wantErrSubstring: "unmarshaling payload data"}, {payload: "{badness", wantErrSubstring: "unmarshaling payload data"},
108107
{payload: `{"payloadType":"notmarshallable}`, wantErrSubstring: "unmarshaling payload data"},
109108
{payload: `{"payload":"shou!ln'twork"}`, wantErrSubstring: "decoding payload"},
110109
{payload: `{"payloadType":"finebutnopayload"}`, wantErrSubstring: "could not find payload"},
@@ -119,7 +118,7 @@ func TestFailures(t *testing.T) {
119118
if predicateType == "" {
120119
predicateType = "custom"
121120
}
122-
_, err = AttestationToPayloadJSON(context.TODO(), predicateType, att)
121+
_, _, err = AttestationToPayloadJSON(context.TODO(), predicateType, att)
123122
checkFailure(t, tc.wantErrSubstring, err)
124123
}
125124
}
@@ -130,7 +129,7 @@ func TestFailures(t *testing.T) {
130129
// constructing different attestations there.
131130
func TestErroringPayload(t *testing.T) {
132131
// Payload() call fails
133-
_, err := AttestationToPayloadJSON(context.TODO(), "custom", &failingAttestation{})
132+
_, _, err := AttestationToPayloadJSON(context.TODO(), "custom", &failingAttestation{})
134133
checkFailure(t, "inducing test failure", err)
135134
}
136135
func TestAttestationToPayloadJson(t *testing.T) {
@@ -142,7 +141,7 @@ func TestAttestationToPayloadJson(t *testing.T) {
142141
if err != nil {
143142
t.Fatal("Failed to create static.NewSignature: ", err)
144143
}
145-
jsonBytes, err := AttestationToPayloadJSON(context.TODO(), fileName, ociSig)
144+
jsonBytes, gotPredicateType, err := AttestationToPayloadJSON(context.TODO(), fileName, ociSig)
146145
if err != nil {
147146
t.Fatalf("Failed to convert : %s", err)
148147
}
@@ -153,12 +152,14 @@ func TestAttestationToPayloadJson(t *testing.T) {
153152
t.Fatalf("[%s] Wanted custom statement, can't unmarshal to it: %v", fileName, err)
154153
}
155154
checkPredicateType(t, attestation.CosignCustomProvenanceV01, intoto.PredicateType)
155+
checkPredicateType(t, gotPredicateType, intoto.PredicateType)
156156
case "vuln":
157157
var vulnStatement attestation.CosignVulnStatement
158158
if err := json.Unmarshal(jsonBytes, &vulnStatement); err != nil {
159159
t.Fatalf("[%s] Wanted vuln statement, can't unmarshal to it: %v", fileName, err)
160160
}
161161
checkPredicateType(t, attestation.CosignVulnProvenanceV01, vulnStatement.PredicateType)
162+
checkPredicateType(t, gotPredicateType, vulnStatement.PredicateType)
162163
case "default":
163164
t.Fatal("non supported predicate file")
164165
}

0 commit comments

Comments
 (0)