1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package policy
17
18 import (
19 "context"
20 "strings"
21 "testing"
22 )
23
24 const (
25 customAttestation = `
26 {
27 "_type": "https://in-toto.io/Statement/v0.1",
28 "predicateType": "https://cosign.sigstore.dev/attestation/v1",
29 "subject": [
30 {
31 "name": "registry.local:5000/policy-controller/demo",
32 "digest": {
33 "sha256": "416cc82c76114b1744ea58bcbf2f411a0f2de4b0456703bf1bb83d33656951bc"
34 }
35 }
36 ],
37 "predicate": {
38 "Data": "foobar e2e test",
39 "Timestamp": "2022-04-20T18:17:19Z"
40 }
41 }`
42
43 vulnAttestation = `
44 {
45 "_type": "https://in-toto.io/Statement/v0.1",
46 "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1",
47 "subject": [
48 {
49 "name": "registry.local:5000/policy-controller/demo",
50 "digest": {
51 "sha256": "416cc82c76114b1744ea58bcbf2f411a0f2de4b0456703bf1bb83d33656951bc"
52 }
53 }
54 ],
55 "predicate": {
56 "invocation": {
57 "parameters": null,
58 "uri": "invocation.example.com/cosign-testing",
59 "event_id": "",
60 "builder.id": ""
61 },
62 "scanner": {
63 "uri": "fakescanner.example.com/cosign-testing",
64 "version": "",
65 "db": {
66 "uri": "",
67 "version": ""
68 },
69 "result": null
70 },
71 "metadata": {
72 "scanStartedOn": "2022-04-12T00:00:00Z",
73 "scanFinishedOn": "2022-04-12T00:10:00Z"
74 }
75 }
76 }`
77
78 cipAttestation = "{\"authorityMatches\":{\"keyatt\":{\"signatures\":null,\"attestations\":{\"vuln-key\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}},\"keysignature\":{\"signatures\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}],\"attestations\":null},\"keylessatt\":{\"signatures\":null,\"attestations\":{\"custom-keyless\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}}}}"
79 )
80
81 func TestEvalPolicy(t *testing.T) {
82
83
84 tests := []struct {
85 name string
86 json string
87 policyType string
88 policyFile string
89 wantErr bool
90 wantErrSub string
91 wantWarnSub string
92 }{{
93 name: "custom attestation, mismatched predicateType",
94 json: customAttestation,
95 policyType: "cue",
96 policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1"`,
97 wantErr: true,
98 wantErrSub: `conflicting values "https://cosign.sigstore.dev/attestation/v1" and "https://cosign.sigstore.dev/attestation/vuln/v1"`,
99 }, {
100 name: "custom attestation, predicateType and data checks out",
101 json: customAttestation,
102 policyType: "cue",
103 policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/v1"
104 predicate: Data: "foobar e2e test"`,
105 }, {
106 name: "custom attestation, data mismatch",
107 json: customAttestation,
108 policyType: "cue",
109 policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/v1"
110 predicate: Data: "invalid data here"`,
111 wantErr: true,
112 wantErrSub: `predicate.Data: conflicting values "foobar e2e test" and "invalid data here"`,
113 }, {
114 name: "vuln attestation, wrong invocation url",
115 json: vulnAttestation,
116 policyType: "cue",
117 policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1"
118 predicate: invocation: uri: "invocation.example.com/wrong-url-here"`,
119 wantErr: true,
120 wantErrSub: `conflicting values "invocation.example.com/cosign-testing" and "invocation.example.com/wrong-url-here"`,
121 }, {
122 name: "vuln attestation, checks out",
123 json: vulnAttestation,
124 policyType: "cue",
125 policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1"
126 predicate: invocation: uri: "invocation.example.com/cosign-testing"`,
127 }, {
128 name: "cluster image policy main policy, checks out",
129 json: cipAttestation,
130 policyType: "cue",
131 policyFile: `package sigstore
132 import "struct"
133 import "list"
134 authorityMatches: {
135 keyatt: {
136 attestations: struct.MaxFields(1) & struct.MinFields(1)
137 },
138 keysignature: {
139 signatures: list.MaxItems(1) & list.MinItems(1)
140 },
141 keylessatt: {
142 attestations: struct.MaxFields(1) & struct.MinFields(1)
143 },
144 keylesssignature: {
145 signatures: list.MaxItems(1) & list.MinItems(1)
146 }
147 }`,
148 }, {
149 name: "cluster image policy main policy, fails",
150 json: cipAttestation,
151 policyType: "cue",
152 wantErr: true,
153 wantErrSub: `failed evaluating cue policy for cluster image policy main policy, fails: failed to evaluate the policy with error: authorityMatches.keylessattMinAttestations: conflicting values 2 and "Error" (mismatched types int and string)`,
154 policyFile: `package sigstore
155 import "struct"
156 import "list"
157 authorityMatches: {
158 keyatt: {
159 attestations: struct.MaxFields(1) & struct.MinFields(1)
160 },
161 keysignature: {
162 signatures: list.MaxItems(1) & list.MinItems(1)
163 },
164 if( len(authorityMatches.keylessatt.attestations) < 2) {
165 keylessattMinAttestations: 2
166 keylessattMinAttestations: "Error"
167 },
168 keylesssignature: {
169 signatures: list.MaxItems(1) & list.MinItems(1)
170 }
171 }`}, {
172 name: "Rego cluster image policy main policy, checks out",
173 json: cipAttestation,
174 policyType: "rego",
175 policyFile: `package sigstore
176 default isCompliant = false
177 isCompliant {
178 attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
179 count(attestationsKeylessATT) == 1
180 attestationsKeyATT := input.authorityMatches.keyatt.attestations
181 count(attestationsKeyATT) == 1
182 keySignature := input.authorityMatches.keysignature.signatures
183 count(keySignature) == 1
184 }`,
185 },
186 {
187 name: "Rego cluster image policy main policy, fails",
188 json: cipAttestation,
189 policyType: "rego",
190 wantErr: true,
191 wantErrSub: `failed evaluating rego policy for type Rego cluster image policy main policy, fails: policy is not compliant for query 'isCompliant = data.sigstore.isCompliant'`,
192 policyFile: `package sigstore
193 default isCompliant = false
194 isCompliant {
195 attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
196 count(attestationsKeylessATT) == 2
197 attestationsKeyATT := input.authorityMatches.keyatt.attestations
198 count(attestationsKeyATT) == 1
199 keySignature := input.authorityMatches.keysignature.signatures
200 count(keySignature) == 1
201 }`,
202 }, {
203 name: "Rego cluster image policy main policy succeed with empty error msg",
204 json: cipAttestation,
205 policyType: "rego",
206 policyFile: `package sigstore
207 isCompliant[response] {
208 attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
209 result = (count(attestationsKeylessATT) == 1)
210 attestationsKeyATT := input.authorityMatches.keyatt.attestations
211 result = (count(attestationsKeyATT) == 1)
212 keySignature := input.authorityMatches.keysignature.signatures
213 result = (count(keySignature) == 1)
214
215 errorMsg = ""
216 warnMsg = ""
217
218 response := {
219 "result" : result,
220 "error" : errorMsg,
221 "warning" : warnMsg
222 }
223 }`,
224 }, {
225 name: "Rego cluster image policy main policy, fails with custom error msg",
226 json: cipAttestation,
227 policyType: "rego",
228 wantErr: true,
229 wantErrSub: `Not found expected list of attestations`,
230 policyFile: `package sigstore
231 isCompliant[response] {
232 attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
233 result = (count(attestationsKeylessATT) == 1000)
234
235 errorMsg = "Not found expected list of attestations"
236 warnMsg = ""
237
238 response := {
239 "result" : result,
240 "error" : errorMsg,
241 "warning" : warnMsg
242 }
243 }`,
244 }, {
245 name: "Rego cluster image policy main policy, returns a custom warning msg",
246 json: cipAttestation,
247 policyType: "rego",
248 wantErr: false,
249 wantWarnSub: `Throw warning error even if succeeded`,
250 policyFile: `package sigstore
251 isCompliant[response] {
252 attestationsKeylessATT := input.authorityMatches.keylessatt.attestations
253 result = (count(attestationsKeylessATT) == 1)
254 attestationsKeyATT := input.authorityMatches.keyatt.attestations
255 result = (count(attestationsKeyATT) == 1)
256 keySignature := input.authorityMatches.keysignature.signatures
257 result = (count(keySignature) == 1)
258
259 errorMsg = ""
260 warnMsg = "Throw warning error even if succeeded"
261
262 response := {
263 "result" : result,
264 "error" : errorMsg,
265 "warning" : warnMsg
266 }
267 }`,
268 }}
269 for _, tc := range tests {
270 ctx := context.Background()
271 warn, err := EvaluatePolicyAgainstJSON(ctx, tc.name, tc.policyType, tc.policyFile, []byte(tc.json))
272 if tc.wantErr {
273 if err == nil {
274 t.Errorf("Did not get an error, wanted %s", tc.wantErrSub)
275 } else if !strings.Contains(err.Error(), tc.wantErrSub) {
276 t.Errorf("Unexpected error, want: %s got: %s", tc.wantErrSub, err.Error())
277 }
278 if tc.wantWarnSub != "" && !strings.Contains(warn.Error(), tc.wantWarnSub) {
279 t.Errorf("Unexpected warning, want: %s got: %s", tc.wantErrSub, err.Error())
280 }
281 } else {
282 if !tc.wantErr && err != nil {
283 t.Errorf("Unexpected error, wanted none, got: %s", err.Error())
284 }
285 if tc.wantWarnSub != "" && !strings.Contains(warn.Error(), tc.wantWarnSub) {
286 t.Errorf("Unexpected warning, want: %s got: %s", tc.wantWarnSub, warn.Error())
287 }
288 }
289 }
290 }
291
View as plain text