1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package test
19
20 import (
21 "bytes"
22 "context"
23 "crypto"
24 "encoding/json"
25 "fmt"
26 "io"
27 "net/http"
28 "net/http/httptest"
29 "net/url"
30 "os"
31 "path"
32 "path/filepath"
33 "testing"
34 "time"
35
36 "github.com/google/go-cmp/cmp"
37 "github.com/google/go-containerregistry/pkg/name"
38 "github.com/google/go-containerregistry/pkg/v1/remote"
39 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40 k8s "k8s.io/client-go/kubernetes"
41 "k8s.io/client-go/tools/clientcmd"
42
43
44 _ "k8s.io/client-go/plugin/pkg/client/auth"
45
46 "github.com/sigstore/cosign/v2/cmd/cosign/cli"
47 "github.com/sigstore/cosign/v2/cmd/cosign/cli/attach"
48 "github.com/sigstore/cosign/v2/cmd/cosign/cli/attest"
49 "github.com/sigstore/cosign/v2/cmd/cosign/cli/dockerfile"
50 "github.com/sigstore/cosign/v2/cmd/cosign/cli/download"
51 "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
52 "github.com/sigstore/cosign/v2/cmd/cosign/cli/manifest"
53 "github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
54 "github.com/sigstore/cosign/v2/cmd/cosign/cli/publickey"
55 "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
56 cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify"
57 "github.com/sigstore/cosign/v2/internal/pkg/cosign/fulcio/fulcioroots"
58 "github.com/sigstore/cosign/v2/pkg/cosign"
59 "github.com/sigstore/cosign/v2/pkg/cosign/bundle"
60 "github.com/sigstore/cosign/v2/pkg/cosign/env"
61 "github.com/sigstore/cosign/v2/pkg/cosign/kubernetes"
62 "github.com/sigstore/cosign/v2/pkg/oci/mutate"
63 ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
64 "github.com/sigstore/sigstore/pkg/signature/payload"
65 tsaclient "github.com/sigstore/timestamp-authority/pkg/client"
66 "github.com/sigstore/timestamp-authority/pkg/server"
67 "github.com/spf13/viper"
68 )
69
70 func TestSignVerify(t *testing.T) {
71 td := t.TempDir()
72 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
73 if err != nil {
74 t.Fatal(err)
75 }
76
77 repo, stop := reg(t)
78 defer stop()
79
80 imgName := path.Join(repo, "cosign-e2e")
81
82 _, _, cleanup := mkimage(t, imgName)
83 defer cleanup()
84
85 _, privKeyPath, pubKeyPath := keypair(t, td)
86
87 ctx := context.Background()
88
89 mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t)
90
91 mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
92
93
94 ko := options.KeyOpts{
95 KeyRef: privKeyPath,
96 PassFunc: passFunc,
97 RekorURL: rekorURL,
98 SkipConfirmation: true,
99 }
100 so := options.SignOptions{
101 Upload: true,
102 TlogUpload: true,
103 }
104 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
105
106
107 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
108 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
109
110
111 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t)
112
113 so.AnnotationOptions = options.AnnotationOptions{
114 Annotations: []string{"foo=bar"},
115 }
116
117 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
118
119
120 must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t)
121
122
123 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", false), t)
124 }
125
126 func TestSignVerifyClean(t *testing.T) {
127 td := t.TempDir()
128 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
129 if err != nil {
130 t.Fatal(err)
131 }
132
133 repo, stop := reg(t)
134 defer stop()
135
136 imgName := path.Join(repo, "cosign-e2e")
137
138 _, _, _ = mkimage(t, imgName)
139
140 _, privKeyPath, pubKeyPath := keypair(t, td)
141
142 ctx := context.Background()
143
144
145 ko := options.KeyOpts{
146 KeyRef: privKeyPath,
147 PassFunc: passFunc,
148 RekorURL: rekorURL,
149 SkipConfirmation: true,
150 }
151 so := options.SignOptions{
152 Upload: true,
153 TlogUpload: true,
154 }
155 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
156
157
158 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
159 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
160
161
162 must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t)
163
164
165 mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t)
166 }
167
168 func TestImportSignVerifyClean(t *testing.T) {
169 td := t.TempDir()
170 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
171 if err != nil {
172 t.Fatal(err)
173 }
174
175 repo, stop := reg(t)
176 defer stop()
177
178 imgName := path.Join(repo, "cosign-e2e")
179
180 _, _, _ = mkimage(t, imgName)
181
182 _, privKeyPath, pubKeyPath := importKeyPair(t, td)
183
184 ctx := context.Background()
185
186
187 ko := options.KeyOpts{
188 KeyRef: privKeyPath,
189 PassFunc: passFunc,
190 RekorURL: rekorURL,
191 SkipConfirmation: true,
192 }
193 so := options.SignOptions{
194 Upload: true,
195 TlogUpload: true,
196 }
197 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
198
199
200 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
201 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
202
203
204 must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t)
205
206
207 mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t)
208 }
209
210 func TestAttestVerify(t *testing.T) {
211 attestVerify(t,
212 "slsaprovenance",
213 `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`,
214 `predicate: builder: id: "2"`,
215 `predicate: builder: id: "1"`,
216 )
217 }
218
219 func TestAttestVerifySPDXJSON(t *testing.T) {
220 attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.spdx.json")
221 if err != nil {
222 t.Fatal(err)
223 }
224 attestVerify(t,
225 "spdxjson",
226 string(attestationBytes),
227 `predicate: spdxVersion: "SPDX-2.2"`,
228 `predicate: spdxVersion: "SPDX-9.9"`,
229 )
230 }
231
232 func TestAttestVerifyCycloneDXJSON(t *testing.T) {
233 attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.cyclonedx.json")
234 if err != nil {
235 t.Fatal(err)
236 }
237 attestVerify(t,
238 "cyclonedx",
239 string(attestationBytes),
240 `predicate: specVersion: "1.4"`,
241 `predicate: specVersion: "7.7"`,
242 )
243 }
244
245 func TestAttestVerifyURI(t *testing.T) {
246 attestationBytes, err := os.ReadFile("./testdata/test-result.json")
247 if err != nil {
248 t.Fatal(err)
249 }
250 attestVerify(t,
251 "https://example.com/TestResult/v1",
252 string(attestationBytes),
253 `predicate: passed: true`,
254 `predicate: passed: false"`,
255 )
256 }
257
258 func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue string) {
259 repo, stop := reg(t)
260 defer stop()
261 td := t.TempDir()
262
263 var imgName, attestationPath string
264 if _, err := url.ParseRequestURI(predicateType); err == nil {
265
266 imgName = path.Join(repo, "cosign-attest-uri-e2e-image")
267 attestationPath = filepath.Join(td, "cosign-attest-uri-e2e-attestation")
268 } else {
269 imgName = path.Join(repo, fmt.Sprintf("cosign-attest-%s-e2e-image", predicateType))
270 attestationPath = filepath.Join(td, fmt.Sprintf("cosign-attest-%s-e2e-attestation", predicateType))
271 }
272
273 _, _, cleanup := mkimage(t, imgName)
274 defer cleanup()
275
276 _, privKeyPath, pubKeyPath := keypair(t, td)
277
278 ctx := context.Background()
279
280
281 verifyAttestation := cliverify.VerifyAttestationCommand{
282 KeyRef: pubKeyPath,
283 IgnoreTlog: true,
284 MaxWorkers: 10,
285 }
286
287
288 mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)
289
290 if err := os.WriteFile(attestationPath, []byte(attestation), 0600); err != nil {
291 t.Fatal(err)
292 }
293
294
295 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
296 attestCmd := attest.AttestCommand{
297 KeyOpts: ko,
298 PredicatePath: attestationPath,
299 PredicateType: predicateType,
300 Timeout: 30 * time.Second,
301 RekorEntryType: "dsse",
302 }
303 must(attestCmd.Exec(ctx, imgName), t)
304
305
306 policyPath := filepath.Join(td, "policy.cue")
307 verifyAttestation.PredicateType = predicateType
308 verifyAttestation.Policies = []string{policyPath}
309
310
311 if err := os.WriteFile(policyPath, []byte(badCue), 0600); err != nil {
312 t.Fatal(err)
313 }
314 mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t)
315
316
317 if err := os.WriteFile(policyPath, []byte(goodCue), 0600); err != nil {
318 t.Fatal(err)
319 }
320 must(verifyAttestation.Exec(ctx, []string{imgName}), t)
321
322
323 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t)
324 }
325
326 func TestAttestationDownload(t *testing.T) {
327 repo, stop := reg(t)
328 defer stop()
329 td := t.TempDir()
330
331 imgName := path.Join(repo, "cosign-attest-download-e2e")
332
333 _, _, cleanup := mkimage(t, imgName)
334 defer cleanup()
335
336 _, privKeyPath, _ := keypair(t, td)
337 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
338
339 ctx := context.Background()
340
341 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
342 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
343 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
344 t.Fatal(err)
345 }
346
347 vulnAttestation := `
348 {
349 "invocation": {
350 "parameters": null,
351 "uri": "invocation.example.com/cosign-testing",
352 "event_id": "",
353 "builder.id": ""
354 },
355 "scanner": {
356 "uri": "fakescanner.example.com/cosign-testing",
357 "version": "",
358 "db": {
359 "uri": "",
360 "version": ""
361 },
362 "result": null
363 },
364 "metadata": {
365 "scanStartedOn": "2022-04-12T00:00:00Z",
366 "scanFinishedOn": "2022-04-12T00:10:00Z"
367 }
368 }
369 `
370 vulnAttestationPath := filepath.Join(td, "attestation.vuln.json")
371 if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil {
372 t.Fatal(err)
373 }
374
375 ref, err := name.ParseReference(imgName)
376 if err != nil {
377 t.Fatal(err)
378 }
379 regOpts := options.RegistryOptions{}
380 ociremoteOpts, err := regOpts.ClientOpts(ctx)
381 if err != nil {
382 t.Fatal(err)
383 }
384
385
386 attestCommand := attest.AttestCommand{
387 KeyOpts: ko,
388 PredicatePath: slsaAttestationPath,
389 PredicateType: "slsaprovenance",
390 Timeout: 30 * time.Second,
391 Replace: true,
392 RekorEntryType: "dsse",
393 }
394 must(attestCommand.Exec(ctx, imgName), t)
395
396
397 attestCommand = attest.AttestCommand{
398 KeyOpts: ko,
399 PredicatePath: vulnAttestationPath,
400 PredicateType: "vuln",
401 Timeout: 30 * time.Second,
402 Replace: true,
403 RekorEntryType: "dsse",
404 }
405 must(attestCommand.Exec(ctx, imgName), t)
406
407
408 attOpts := options.AttestationDownloadOptions{}
409 must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t)
410
411 attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
412 if err != nil {
413 t.Fatal(err)
414 }
415 if len(attestations) != 2 {
416 t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations)))
417 }
418 }
419
420 func TestAttestationDownloadWithPredicateType(t *testing.T) {
421 repo, stop := reg(t)
422 defer stop()
423 td := t.TempDir()
424
425 imgName := path.Join(repo, "cosign-attest-download-predicate-type-e2e")
426
427 _, _, cleanup := mkimage(t, imgName)
428 defer cleanup()
429
430 _, privKeyPath, _ := keypair(t, td)
431 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
432
433 ctx := context.Background()
434
435 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
436 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
437 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
438 t.Fatal(err)
439 }
440
441 vulnAttestation := `
442 {
443 "invocation": {
444 "parameters": null,
445 "uri": "invocation.example.com/cosign-testing",
446 "event_id": "",
447 "builder.id": ""
448 },
449 "scanner": {
450 "uri": "fakescanner.example.com/cosign-testing",
451 "version": "",
452 "db": {
453 "uri": "",
454 "version": ""
455 },
456 "result": null
457 },
458 "metadata": {
459 "scanStartedOn": "2022-04-12T00:00:00Z",
460 "scanFinishedOn": "2022-04-12T00:10:00Z"
461 }
462 }
463 `
464 vulnAttestationPath := filepath.Join(td, "attestation.vuln.json")
465 if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil {
466 t.Fatal(err)
467 }
468
469 ref, err := name.ParseReference(imgName)
470 if err != nil {
471 t.Fatal(err)
472 }
473 regOpts := options.RegistryOptions{}
474 ociremoteOpts, err := regOpts.ClientOpts(ctx)
475 if err != nil {
476 t.Fatal(err)
477 }
478
479
480 attestCommand := attest.AttestCommand{
481 KeyOpts: ko,
482 PredicatePath: slsaAttestationPath,
483 PredicateType: "slsaprovenance",
484 Timeout: 30 * time.Second,
485 Replace: true,
486 RekorEntryType: "dsse",
487 }
488 must(attestCommand.Exec(ctx, imgName), t)
489
490
491 attestCommand = attest.AttestCommand{
492 KeyOpts: ko,
493 PredicatePath: vulnAttestationPath,
494 PredicateType: "vuln",
495 Timeout: 30 * time.Second,
496 Replace: true,
497 RekorEntryType: "dsse",
498 }
499 must(attestCommand.Exec(ctx, imgName), t)
500
501
502 attOpts := options.AttestationDownloadOptions{
503 PredicateType: "vuln",
504 }
505 must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t)
506
507 predicateType, _ := options.ParsePredicateType(attOpts.PredicateType)
508 attestations, err := cosign.FetchAttestationsForReference(ctx, ref, predicateType, ociremoteOpts...)
509 if err != nil {
510 t.Fatal(err)
511 }
512 if len(attestations) != 1 {
513 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
514 }
515 }
516
517 func TestAttestationDownloadWithBadPredicateType(t *testing.T) {
518 repo, stop := reg(t)
519 defer stop()
520 td := t.TempDir()
521
522 imgName := path.Join(repo, "cosign-attest-download-bad-type-e2e")
523
524 _, _, cleanup := mkimage(t, imgName)
525 defer cleanup()
526
527 _, privKeyPath, _ := keypair(t, td)
528 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
529
530 ctx := context.Background()
531
532 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
533 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
534 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
535 t.Fatal(err)
536 }
537
538 regOpts := options.RegistryOptions{}
539
540
541 attestCommand := attest.AttestCommand{
542 KeyOpts: ko,
543 PredicatePath: slsaAttestationPath,
544 PredicateType: "slsaprovenance",
545 Timeout: 30 * time.Second,
546 Replace: true,
547 RekorEntryType: "dsse",
548 }
549 must(attestCommand.Exec(ctx, imgName), t)
550
551
552 attOpts := options.AttestationDownloadOptions{
553 PredicateType: "vuln",
554 }
555 mustErr(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t)
556 }
557
558 func TestAttestationReplaceCreate(t *testing.T) {
559 repo, stop := reg(t)
560 defer stop()
561 td := t.TempDir()
562
563 imgName := path.Join(repo, "cosign-attest-replace-e2e")
564
565 _, _, cleanup := mkimage(t, imgName)
566 defer cleanup()
567
568 _, privKeyPath, _ := keypair(t, td)
569 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
570
571 ctx := context.Background()
572
573 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
574 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
575 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
576 t.Fatal(err)
577 }
578
579 ref, err := name.ParseReference(imgName)
580 if err != nil {
581 t.Fatal(err)
582 }
583 regOpts := options.RegistryOptions{}
584 ociremoteOpts, err := regOpts.ClientOpts(ctx)
585 if err != nil {
586 t.Fatal(err)
587 }
588
589
590 attestCommand := attest.AttestCommand{
591 KeyOpts: ko,
592 PredicatePath: slsaAttestationPath,
593 PredicateType: "slsaprovenance",
594 Timeout: 30 * time.Second,
595 Replace: true,
596 RekorEntryType: "dsse",
597 }
598 must(attestCommand.Exec(ctx, imgName), t)
599
600
601 attOpts := options.AttestationDownloadOptions{}
602 attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
603 if err != nil {
604 t.Fatal(err)
605 }
606 if len(attestations) != 1 {
607 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
608 }
609 }
610
611 func TestAttestationReplace(t *testing.T) {
612 repo, stop := reg(t)
613 defer stop()
614 td := t.TempDir()
615
616 imgName := path.Join(repo, "cosign-attest-replace-e2e")
617
618 _, _, cleanup := mkimage(t, imgName)
619 defer cleanup()
620
621 _, privKeyPath, _ := keypair(t, td)
622 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
623
624 ctx := context.Background()
625
626 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
627 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
628 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
629 t.Fatal(err)
630 }
631
632 ref, err := name.ParseReference(imgName)
633 if err != nil {
634 t.Fatal(err)
635 }
636 regOpts := options.RegistryOptions{}
637 ociremoteOpts, err := regOpts.ClientOpts(ctx)
638 if err != nil {
639 t.Fatal(err)
640 }
641
642
643 attestCommand := attest.AttestCommand{
644 KeyOpts: ko,
645 PredicatePath: slsaAttestationPath,
646 PredicateType: "slsaprovenance",
647 Timeout: 30 * time.Second,
648 RekorEntryType: "dsse",
649 }
650 must(attestCommand.Exec(ctx, imgName), t)
651
652
653 attOpts := options.AttestationDownloadOptions{}
654 attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
655 if err != nil {
656 t.Fatal(err)
657 }
658 if len(attestations) != 1 {
659 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
660 }
661
662
663 attestCommand = attest.AttestCommand{
664 KeyOpts: ko,
665 PredicatePath: slsaAttestationPath,
666 PredicateType: "slsaprovenance",
667 Replace: true,
668 Timeout: 30 * time.Second,
669 RekorEntryType: "dsse",
670 }
671 must(attestCommand.Exec(ctx, imgName), t)
672 attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
673
674
675 if err != nil {
676 t.Fatal(err)
677 }
678 if len(attestations) != 1 {
679 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
680 }
681
682
683 attestCommand = attest.AttestCommand{
684 KeyOpts: ko,
685 PredicatePath: slsaAttestationPath,
686 PredicateType: "custom",
687 Replace: true,
688 Timeout: 30 * time.Second,
689 RekorEntryType: "dsse",
690 }
691 must(attestCommand.Exec(ctx, imgName), t)
692
693
694 attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
695 if err != nil {
696 t.Fatal(err)
697 }
698 if len(attestations) != 2 {
699 t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations)))
700 }
701 }
702
703 func TestAttestationRFC3161Timestamp(t *testing.T) {
704
705 viper.Set("timestamp-signer", "memory")
706 viper.Set("timestamp-signer-hash", "sha256")
707 apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
708 server := httptest.NewServer(apiServer.GetHandler())
709 t.Cleanup(server.Close)
710
711 repo, stop := reg(t)
712 defer stop()
713 td := t.TempDir()
714
715 imgName := path.Join(repo, "cosign-attest-timestamp-e2e")
716
717 _, _, cleanup := mkimage(t, imgName)
718 defer cleanup()
719
720 _, privKeyPath, pubKeyPath := keypair(t, td)
721 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
722
723 ctx := context.Background()
724
725 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
726 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
727 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
728 t.Fatal(err)
729 }
730
731 ref, err := name.ParseReference(imgName)
732 if err != nil {
733 t.Fatal(err)
734 }
735 regOpts := options.RegistryOptions{}
736 ociremoteOpts, err := regOpts.ClientOpts(ctx)
737 if err != nil {
738 t.Fatal(err)
739 }
740
741
742 attestCommand := attest.AttestCommand{
743 KeyOpts: ko,
744 PredicatePath: slsaAttestationPath,
745 PredicateType: "slsaprovenance",
746 Timeout: 30 * time.Second,
747 TSAServerURL: server.URL + "/api/v1/timestamp",
748 TlogUpload: false,
749 RekorEntryType: "dsse",
750 }
751 must(attestCommand.Exec(ctx, imgName), t)
752
753
754 attOpts := options.AttestationDownloadOptions{}
755 attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...)
756 if err != nil {
757 t.Fatal(err)
758 }
759 if len(attestations) != 1 {
760 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations)))
761 }
762
763 client, err := tsaclient.GetTimestampClient(server.URL)
764 if err != nil {
765 t.Error(err)
766 }
767
768 chain, err := client.Timestamp.GetTimestampCertChain(nil)
769 if err != nil {
770 t.Fatalf("unexpected error getting timestamp chain: %v", err)
771 }
772
773 file, err := os.CreateTemp(os.TempDir(), "tempfile")
774 if err != nil {
775 t.Fatalf("error creating temp file: %v", err)
776 }
777 defer os.Remove(file.Name())
778 _, err = file.WriteString(chain.Payload)
779 if err != nil {
780 t.Fatalf("error writing chain payload to temp file: %v", err)
781 }
782
783 verifyAttestation := cliverify.VerifyAttestationCommand{
784 KeyRef: pubKeyPath,
785 TSACertChainPath: file.Name(),
786 IgnoreTlog: true,
787 PredicateType: "slsaprovenance",
788 MaxWorkers: 10,
789 }
790
791 must(verifyAttestation.Exec(ctx, []string{imgName}), t)
792 }
793
794 func TestRekorBundle(t *testing.T) {
795 td := t.TempDir()
796 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
797 if err != nil {
798 t.Fatal(err)
799 }
800
801 repo, stop := reg(t)
802 defer stop()
803
804 imgName := path.Join(repo, "cosign-e2e")
805
806 _, _, cleanup := mkimage(t, imgName)
807 defer cleanup()
808
809 _, privKeyPath, pubKeyPath := keypair(t, td)
810
811 ko := options.KeyOpts{
812 KeyRef: privKeyPath,
813 PassFunc: passFunc,
814 RekorURL: rekorURL,
815 SkipConfirmation: true,
816 }
817 so := options.SignOptions{
818 Upload: true,
819 TlogUpload: true,
820 }
821
822
823 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
824
825 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
826
827
828 must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t)
829 }
830
831 func TestRekorOutput(t *testing.T) {
832 td := t.TempDir()
833 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
834 if err != nil {
835 t.Fatal(err)
836 }
837
838 repo, stop := reg(t)
839 defer stop()
840
841 imgName := path.Join(repo, "cosign-e2e")
842 bundlePath := filepath.Join(td, "bundle.sig")
843
844 _, _, cleanup := mkimage(t, imgName)
845 defer cleanup()
846
847 _, privKeyPath, pubKeyPath := keypair(t, td)
848
849 ko := options.KeyOpts{
850 KeyRef: privKeyPath,
851 PassFunc: passFunc,
852 RekorURL: rekorURL,
853 BundlePath: bundlePath,
854 }
855 so := options.SignOptions{
856 Upload: true,
857 TlogUpload: true,
858 }
859
860
861 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
862
863 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
864
865 if file, err := os.ReadFile(bundlePath); err != nil {
866 t.Fatal(err)
867 } else {
868 var localCosignPayload cosign.LocalSignedPayload
869 if err := json.Unmarshal(file, &localCosignPayload); err != nil {
870 t.Fatal(err)
871 }
872 }
873
874 must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t)
875 }
876
877 func TestFulcioBundle(t *testing.T) {
878 td := t.TempDir()
879 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
880 if err != nil {
881 t.Fatal(err)
882 }
883
884 repo, stop := reg(t)
885 defer stop()
886
887 imgName := path.Join(repo, "cosign-e2e")
888
889 _, _, cleanup := mkimage(t, imgName)
890 defer cleanup()
891
892 _, privKeyPath, pubKeyPath := keypair(t, td)
893
894 ko := options.KeyOpts{
895 KeyRef: privKeyPath,
896 PassFunc: passFunc,
897 RekorURL: rekorURL,
898 FulcioURL: fulcioURL,
899 SkipConfirmation: true,
900 }
901 so := options.SignOptions{
902 Upload: true,
903 TlogUpload: true,
904 IssueCertificate: true,
905 }
906
907
908 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
909
910 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
911
912
913
914 must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t)
915 }
916
917 func TestRFC3161Timestamp(t *testing.T) {
918
919 viper.Set("timestamp-signer", "memory")
920 viper.Set("timestamp-signer-hash", "sha256")
921 apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
922 server := httptest.NewServer(apiServer.GetHandler())
923 t.Cleanup(server.Close)
924
925 client, err := tsaclient.GetTimestampClient(server.URL)
926 if err != nil {
927 t.Error(err)
928 }
929
930 chain, err := client.Timestamp.GetTimestampCertChain(nil)
931 if err != nil {
932 t.Fatalf("unexpected error getting timestamp chain: %v", err)
933 }
934
935 file, err := os.CreateTemp(os.TempDir(), "tempfile")
936 if err != nil {
937 t.Fatalf("error creating temp file: %v", err)
938 }
939 defer os.Remove(file.Name())
940 _, err = file.WriteString(chain.Payload)
941 if err != nil {
942 t.Fatalf("error writing chain payload to temp file: %v", err)
943 }
944
945 repo, stop := reg(t)
946 defer stop()
947 td := t.TempDir()
948
949 imgName := path.Join(repo, "cosign-e2e")
950
951 _, _, cleanup := mkimage(t, imgName)
952 defer cleanup()
953
954 _, privKeyPath, pubKeyPath := keypair(t, td)
955
956 ko := options.KeyOpts{
957 KeyRef: privKeyPath,
958 PassFunc: passFunc,
959 TSAServerURL: server.URL + "/api/v1/timestamp",
960 }
961 so := options.SignOptions{
962 Upload: true,
963 TlogUpload: false,
964 }
965
966
967 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
968
969 must(verifyTSA(pubKeyPath, imgName, true, nil, "", file.Name(), true), t)
970 }
971
972 func TestRekorBundleAndRFC3161Timestamp(t *testing.T) {
973 td := t.TempDir()
974 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
975 if err != nil {
976 t.Fatal(err)
977 }
978
979
980 viper.Set("timestamp-signer", "memory")
981 viper.Set("timestamp-signer-hash", "sha256")
982 apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
983 server := httptest.NewServer(apiServer.GetHandler())
984 t.Cleanup(server.Close)
985
986 client, err := tsaclient.GetTimestampClient(server.URL)
987 if err != nil {
988 t.Error(err)
989 }
990
991 chain, err := client.Timestamp.GetTimestampCertChain(nil)
992 if err != nil {
993 t.Fatalf("unexpected error getting timestamp chain: %v", err)
994 }
995
996 file, err := os.CreateTemp(os.TempDir(), "tempfile")
997 if err != nil {
998 t.Fatalf("error creating temp file: %v", err)
999 }
1000 defer os.Remove(file.Name())
1001 _, err = file.WriteString(chain.Payload)
1002 if err != nil {
1003 t.Fatalf("error writing chain payload to temp file: %v", err)
1004 }
1005
1006 repo, stop := reg(t)
1007 defer stop()
1008
1009 imgName := path.Join(repo, "cosign-e2e")
1010
1011 _, _, cleanup := mkimage(t, imgName)
1012 defer cleanup()
1013
1014 _, privKeyPath, pubKeyPath := keypair(t, td)
1015
1016 ko := options.KeyOpts{
1017 KeyRef: privKeyPath,
1018 PassFunc: passFunc,
1019 TSAServerURL: server.URL + "/api/v1/timestamp",
1020 RekorURL: rekorURL,
1021 SkipConfirmation: true,
1022 }
1023 so := options.SignOptions{
1024 Upload: true,
1025 TlogUpload: true,
1026 }
1027
1028
1029 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1030
1031 must(verifyTSA(pubKeyPath, imgName, true, nil, "", file.Name(), false), t)
1032 }
1033
1034 func TestDuplicateSign(t *testing.T) {
1035 td := t.TempDir()
1036 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1037 if err != nil {
1038 t.Fatal(err)
1039 }
1040
1041 repo, stop := reg(t)
1042 defer stop()
1043
1044 imgName := path.Join(repo, "cosign-e2e")
1045
1046 ref, _, cleanup := mkimage(t, imgName)
1047 defer cleanup()
1048
1049 _, privKeyPath, pubKeyPath := keypair(t, td)
1050
1051 ctx := context.Background()
1052
1053 mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t)
1054
1055 mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
1056
1057
1058 ko := options.KeyOpts{
1059 KeyRef: privKeyPath,
1060 PassFunc: passFunc,
1061 }
1062 so := options.SignOptions{
1063 Upload: true,
1064 }
1065 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1066
1067
1068
1069 must(verify(pubKeyPath, imgName, true, nil, "", true), t)
1070 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t)
1071
1072
1073 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1074
1075 se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...))
1076 must(err, t)
1077 sigs, err := se.Signatures()
1078 must(err, t)
1079 signatures, err := sigs.Get()
1080 must(err, t)
1081
1082 if len(signatures) > 1 {
1083 t.Errorf("expected there to only be one signature, got %v", signatures)
1084 }
1085 }
1086
1087 func TestKeyURLVerify(t *testing.T) {
1088
1089 t.Skip()
1090
1091 keyRef := "https://raw.githubusercontent.com/GoogleContainerTools/distroless/main/cosign.pub"
1092 img := "gcr.io/distroless/base:latest"
1093
1094 must(verify(keyRef, img, true, nil, "", false), t)
1095 }
1096
1097 func TestGenerateKeyPairEnvVar(t *testing.T) {
1098 t.Setenv("COSIGN_PASSWORD", "foo")
1099 keys, err := cosign.GenerateKeyPair(generate.GetPass)
1100 if err != nil {
1101 t.Fatal(err)
1102 }
1103 if _, err := cosign.LoadPrivateKey(keys.PrivateBytes, []byte("foo")); err != nil {
1104 t.Fatal(err)
1105 }
1106 }
1107
1108 func TestGenerateKeyPairK8s(t *testing.T) {
1109 td := t.TempDir()
1110 wd, err := os.Getwd()
1111 if err != nil {
1112 t.Fatal(err)
1113 }
1114 if err := os.Chdir(td); err != nil {
1115 t.Fatal(err)
1116 }
1117 defer func() {
1118 os.Chdir(wd)
1119 }()
1120 password := "foo"
1121 t.Setenv("COSIGN_PASSWORD", password)
1122 ctx := context.Background()
1123 name := "cosign-secret"
1124 namespace := "default"
1125 if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("k8s://%s/%s", namespace, name), generate.GetPass); err != nil {
1126 t.Fatal(err)
1127 }
1128
1129
1130 cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
1131 clientcmd.NewDefaultClientConfigLoadingRules(), nil).ClientConfig()
1132 if err != nil {
1133 t.Fatal(err)
1134 }
1135 client, err := k8s.NewForConfig(cfg)
1136 if err != nil {
1137 t.Fatal(err)
1138 }
1139 s, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
1140 if err != nil {
1141 t.Fatal(err)
1142 }
1143 if v, ok := s.Data["cosign.password"]; !ok || string(v) != password {
1144 t.Fatalf("password is incorrect, got %v expected %v", v, "foo")
1145 }
1146
1147 err = client.CoreV1().Secrets(namespace).Delete(ctx, name, metav1.DeleteOptions{})
1148 if err != nil {
1149 t.Fatal(err)
1150 }
1151 }
1152
1153 func TestMultipleSignatures(t *testing.T) {
1154 td := t.TempDir()
1155 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1156 if err != nil {
1157 t.Fatal(err)
1158 }
1159
1160 repo, stop := reg(t)
1161 defer stop()
1162
1163 td1 := t.TempDir()
1164 td2 := t.TempDir()
1165
1166 imgName := path.Join(repo, "cosign-e2e")
1167
1168 _, _, cleanup := mkimage(t, imgName)
1169 defer cleanup()
1170
1171 _, priv1, pub1 := keypair(t, td1)
1172 _, priv2, pub2 := keypair(t, td2)
1173
1174
1175 mustErr(verify(pub1, imgName, true, nil, "", false), t)
1176 mustErr(verify(pub2, imgName, true, nil, "", false), t)
1177
1178
1179 ko := options.KeyOpts{
1180 KeyRef: priv1,
1181 PassFunc: passFunc,
1182 RekorURL: rekorURL,
1183 SkipConfirmation: true,
1184 }
1185 so := options.SignOptions{
1186 Upload: true,
1187 TlogUpload: true,
1188 }
1189 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1190
1191 must(verify(pub1, imgName, true, nil, "", false), t)
1192 mustErr(verify(pub2, imgName, true, nil, "", false), t)
1193
1194
1195 ko.KeyRef = priv2
1196 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1197
1198
1199 must(verify(pub1, imgName, true, nil, "", false), t)
1200 must(verify(pub2, imgName, true, nil, "", false), t)
1201 }
1202
1203 func TestSignBlob(t *testing.T) {
1204 td := t.TempDir()
1205 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1206 if err != nil {
1207 t.Fatal(err)
1208 }
1209 blob := "someblob"
1210 td1 := t.TempDir()
1211 td2 := t.TempDir()
1212 bp := filepath.Join(td1, blob)
1213
1214 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
1215 t.Fatal(err)
1216 }
1217
1218 _, privKeyPath1, pubKeyPath1 := keypair(t, td1)
1219 _, _, pubKeyPath2 := keypair(t, td2)
1220
1221 ctx := context.Background()
1222
1223 ko1 := options.KeyOpts{
1224 KeyRef: pubKeyPath1,
1225 }
1226 ko2 := options.KeyOpts{
1227 KeyRef: pubKeyPath2,
1228 }
1229
1230 cmd1 := cliverify.VerifyBlobCmd{
1231 KeyOpts: ko1,
1232 SigRef: "badsig",
1233 IgnoreTlog: true,
1234 }
1235 cmd2 := cliverify.VerifyBlobCmd{
1236 KeyOpts: ko2,
1237 SigRef: "badsig",
1238 IgnoreTlog: true,
1239 }
1240 mustErr(cmd1.Exec(ctx, blob), t)
1241 mustErr(cmd2.Exec(ctx, blob), t)
1242
1243
1244 ko := options.KeyOpts{
1245 KeyRef: privKeyPath1,
1246 PassFunc: passFunc,
1247 }
1248 sig, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false)
1249 if err != nil {
1250 t.Fatal(err)
1251 }
1252
1253 cmd1.SigRef = string(sig)
1254 cmd2.SigRef = string(sig)
1255 must(cmd1.Exec(ctx, bp), t)
1256 mustErr(cmd2.Exec(ctx, bp), t)
1257 }
1258
1259 func TestSignBlobBundle(t *testing.T) {
1260 blob := "someblob"
1261 td1 := t.TempDir()
1262 bp := filepath.Join(td1, blob)
1263 bundlePath := filepath.Join(td1, "bundle.sig")
1264
1265 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
1266 t.Fatal(err)
1267 }
1268
1269 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td1)
1270 if err != nil {
1271 t.Fatal(err)
1272 }
1273
1274 _, privKeyPath1, pubKeyPath1 := keypair(t, td1)
1275
1276 ctx := context.Background()
1277
1278 ko1 := options.KeyOpts{
1279 KeyRef: pubKeyPath1,
1280 BundlePath: bundlePath,
1281 }
1282
1283 verifyBlobCmd := cliverify.VerifyBlobCmd{
1284 KeyOpts: ko1,
1285 IgnoreTlog: true,
1286 }
1287 mustErr(verifyBlobCmd.Exec(ctx, bp), t)
1288
1289
1290 ko := options.KeyOpts{
1291 KeyRef: privKeyPath1,
1292 PassFunc: passFunc,
1293 BundlePath: bundlePath,
1294 RekorURL: rekorURL,
1295 SkipConfirmation: true,
1296 }
1297 if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil {
1298 t.Fatal(err)
1299 }
1300
1301 must(verifyBlobCmd.Exec(ctx, bp), t)
1302
1303
1304 if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", true); err != nil {
1305 t.Fatal(err)
1306 }
1307
1308
1309 verifyBlobCmd.RekorURL = "notreal"
1310 verifyBlobCmd.IgnoreTlog = false
1311 must(verifyBlobCmd.Exec(ctx, bp), t)
1312 }
1313
1314 func TestSignBlobRFC3161TimestampBundle(t *testing.T) {
1315 td := t.TempDir()
1316 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1317 if err != nil {
1318 t.Fatal(err)
1319 }
1320
1321 viper.Set("timestamp-signer", "memory")
1322 viper.Set("timestamp-signer-hash", "sha256")
1323 apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
1324 server := httptest.NewServer(apiServer.GetHandler())
1325 t.Cleanup(server.Close)
1326
1327 blob := "someblob"
1328 bp := filepath.Join(td, blob)
1329 bundlePath := filepath.Join(td, "bundle.sig")
1330 tsPath := filepath.Join(td, "rfc3161Timestamp.json")
1331
1332 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
1333 t.Fatal(err)
1334 }
1335
1336 client, err := tsaclient.GetTimestampClient(server.URL)
1337 if err != nil {
1338 t.Error(err)
1339 }
1340
1341 chain, err := client.Timestamp.GetTimestampCertChain(nil)
1342 if err != nil {
1343 t.Fatalf("unexpected error getting timestamp chain: %v", err)
1344 }
1345
1346 file, err := os.CreateTemp(os.TempDir(), "tempfile")
1347 if err != nil {
1348 t.Fatalf("error creating temp file: %v", err)
1349 }
1350 defer os.Remove(file.Name())
1351 _, err = file.WriteString(chain.Payload)
1352 if err != nil {
1353 t.Fatalf("error writing chain payload to temp file: %v", err)
1354 }
1355
1356 _, privKeyPath1, pubKeyPath1 := keypair(t, td)
1357
1358 ctx := context.Background()
1359
1360 ko1 := options.KeyOpts{
1361 KeyRef: pubKeyPath1,
1362 BundlePath: bundlePath,
1363 RFC3161TimestampPath: tsPath,
1364 TSACertChainPath: file.Name(),
1365 }
1366
1367 verifyBlobCmd := cliverify.VerifyBlobCmd{
1368 KeyOpts: ko1,
1369 IgnoreTlog: true,
1370 }
1371 mustErr(verifyBlobCmd.Exec(ctx, bp), t)
1372
1373
1374 ko := options.KeyOpts{
1375 KeyRef: privKeyPath1,
1376 PassFunc: passFunc,
1377 BundlePath: bundlePath,
1378 RFC3161TimestampPath: tsPath,
1379 TSAServerURL: server.URL + "/api/v1/timestamp",
1380 RekorURL: rekorURL,
1381 SkipConfirmation: true,
1382 }
1383 if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil {
1384 t.Fatal(err)
1385 }
1386
1387 must(verifyBlobCmd.Exec(ctx, bp), t)
1388
1389
1390 if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", true); err != nil {
1391 t.Fatal(err)
1392 }
1393
1394 verifyBlobCmd.RekorURL = "notreal"
1395 verifyBlobCmd.IgnoreTlog = false
1396 must(verifyBlobCmd.Exec(ctx, bp), t)
1397 }
1398
1399 func TestGenerate(t *testing.T) {
1400 repo, stop := reg(t)
1401 defer stop()
1402
1403 imgName := path.Join(repo, "cosign-e2e")
1404 _, desc, cleanup := mkimage(t, imgName)
1405 defer cleanup()
1406
1407
1408 b := bytes.Buffer{}
1409 must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t)
1410 ss := payload.SimpleContainerImage{}
1411 must(json.Unmarshal(b.Bytes(), &ss), t)
1412
1413 equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
1414
1415
1416 b.Reset()
1417 a := map[string]interface{}{"foo": "bar"}
1418 must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, a, &b), t)
1419 must(json.Unmarshal(b.Bytes(), &ss), t)
1420
1421 equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t)
1422 equals(ss.Optional["foo"], "bar", t)
1423 }
1424
1425 func TestSaveLoad(t *testing.T) {
1426 td := t.TempDir()
1427 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1428 if err != nil {
1429 t.Fatal(err)
1430 }
1431 tests := []struct {
1432 description string
1433 getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func())
1434 }{
1435 {
1436 description: "save and load an image",
1437 getSignedEntity: mkimage,
1438 },
1439 {
1440 description: "save and load an image index",
1441 getSignedEntity: mkimageindex,
1442 },
1443 }
1444 for i, test := range tests {
1445 t.Run(test.description, func(t *testing.T) {
1446 repo, stop := reg(t)
1447 defer stop()
1448 keysDir := t.TempDir()
1449
1450 imgName := path.Join(repo, fmt.Sprintf("save-load-%d", i))
1451
1452 _, _, cleanup := test.getSignedEntity(t, imgName)
1453 defer cleanup()
1454
1455 _, privKeyPath, pubKeyPath := keypair(t, keysDir)
1456
1457 ctx := context.Background()
1458
1459 ko := options.KeyOpts{
1460 KeyRef: privKeyPath,
1461 PassFunc: passFunc,
1462 RekorURL: rekorURL,
1463 SkipConfirmation: true,
1464 }
1465 so := options.SignOptions{
1466 Upload: true,
1467 TlogUpload: true,
1468 }
1469 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1470 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
1471
1472
1473 imageDir := t.TempDir()
1474 must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t)
1475
1476
1477 must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t)
1478
1479
1480 imgName2 := path.Join(repo, fmt.Sprintf("save-load-%d-2", i))
1481 must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t)
1482 must(verify(pubKeyPath, imgName2, true, nil, "", false), t)
1483 })
1484 }
1485 }
1486
1487 func TestSaveLoadAttestation(t *testing.T) {
1488 td := t.TempDir()
1489 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1490 if err != nil {
1491 t.Fatal(err)
1492 }
1493
1494 repo, stop := reg(t)
1495 defer stop()
1496
1497 imgName := path.Join(repo, "save-load")
1498
1499 _, _, cleanup := mkimage(t, imgName)
1500 defer cleanup()
1501
1502 _, privKeyPath, pubKeyPath := keypair(t, td)
1503
1504 ctx := context.Background()
1505
1506 ko := options.KeyOpts{
1507 KeyRef: privKeyPath,
1508 PassFunc: passFunc,
1509 RekorURL: rekorURL,
1510 SkipConfirmation: true,
1511 }
1512 so := options.SignOptions{
1513 Upload: true,
1514 TlogUpload: true,
1515 }
1516 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1517 must(verify(pubKeyPath, imgName, true, nil, "", false), t)
1518
1519
1520 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
1521 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json")
1522 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil {
1523 t.Fatal(err)
1524 }
1525
1526
1527 ko = options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc}
1528 attestCommand := attest.AttestCommand{
1529 KeyOpts: ko,
1530 PredicatePath: slsaAttestationPath,
1531 PredicateType: "slsaprovenance",
1532 Timeout: 30 * time.Second,
1533 RekorEntryType: "dsse",
1534 }
1535 must(attestCommand.Exec(ctx, imgName), t)
1536
1537
1538 imageDir := t.TempDir()
1539 must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t)
1540
1541
1542 imgName2 := path.Join(repo, "save-load-2")
1543 must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t)
1544 must(verify(pubKeyPath, imgName2, true, nil, "", false), t)
1545
1546 policyPath := filepath.Join(td, "policy.cue")
1547 verifyAttestation := cliverify.VerifyAttestationCommand{
1548 KeyRef: pubKeyPath,
1549 IgnoreTlog: true,
1550 MaxWorkers: 10,
1551 }
1552 verifyAttestation.PredicateType = "slsaprovenance"
1553 verifyAttestation.Policies = []string{policyPath}
1554
1555 cuePolicy := `predicate: builder: id: "2"`
1556 if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil {
1557 t.Fatal(err)
1558 }
1559 must(verifyAttestation.Exec(ctx, []string{imgName2}), t)
1560
1561 verifyAttestation.LocalImage = true
1562 must(verifyAttestation.Exec(ctx, []string{imageDir}), t)
1563 }
1564
1565 func TestAttachSBOM(t *testing.T) {
1566 td := t.TempDir()
1567 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1568 if err != nil {
1569 t.Fatal(err)
1570 }
1571
1572 repo, stop := reg(t)
1573 defer stop()
1574 ctx := context.Background()
1575
1576 imgName := path.Join(repo, "sbom-image")
1577 img, _, cleanup := mkimage(t, imgName)
1578 defer cleanup()
1579
1580 out := bytes.Buffer{}
1581
1582 _, errPl := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{Platform: "darwin/amd64"}, img.Name(), &out)
1583 if errPl == nil {
1584 t.Fatalf("Expected error when passing Platform to single arch image")
1585 }
1586 _, err = download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, img.Name(), &out)
1587 if err == nil {
1588 t.Fatal("Expected error")
1589 }
1590 t.Log(out.String())
1591 out.Reset()
1592
1593
1594 must(attach.SBOMCmd(ctx, options.RegistryOptions{}, options.RegistryExperimentalOptions{}, "./testdata/bom-go-mod.spdx", "spdx", imgName), t)
1595
1596 sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, imgName, &out)
1597 if err != nil {
1598 t.Fatal(err)
1599 }
1600 t.Log(out.String())
1601 if len(sboms) != 1 {
1602 t.Fatalf("Expected one sbom, got %d", len(sboms))
1603 }
1604 want, err := os.ReadFile("./testdata/bom-go-mod.spdx")
1605 if err != nil {
1606 t.Fatal(err)
1607 }
1608 if diff := cmp.Diff(string(want), sboms[0]); diff != "" {
1609 t.Errorf("diff: %s", diff)
1610 }
1611
1612
1613 td1 := t.TempDir()
1614 td2 := t.TempDir()
1615 _, privKeyPath1, pubKeyPath1 := keypair(t, td1)
1616 _, _, pubKeyPath2 := keypair(t, td2)
1617
1618
1619 mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t)
1620 mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t)
1621
1622
1623 ko1 := options.KeyOpts{
1624 KeyRef: privKeyPath1,
1625 PassFunc: passFunc,
1626 RekorURL: rekorURL,
1627 }
1628 so := options.SignOptions{
1629 Upload: true,
1630 TlogUpload: true,
1631 Attachment: "sbom",
1632 }
1633 must(sign.SignCmd(ro, ko1, so, []string{imgName}), t)
1634
1635
1636 must(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t)
1637 mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t)
1638 }
1639
1640 func TestNoTlog(t *testing.T) {
1641 repo, stop := reg(t)
1642 defer stop()
1643 td := t.TempDir()
1644
1645 imgName := path.Join(repo, "cosign-e2e")
1646
1647 _, _, cleanup := mkimage(t, imgName)
1648 defer cleanup()
1649
1650 _, privKeyPath, pubKeyPath := keypair(t, td)
1651
1652
1653 mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t)
1654
1655
1656 ko := options.KeyOpts{
1657 KeyRef: privKeyPath,
1658 PassFunc: passFunc,
1659 RekorURL: rekorURL,
1660 }
1661 so := options.SignOptions{
1662 Upload: true,
1663 }
1664 must(sign.SignCmd(ro, ko, so, []string{imgName}), t)
1665
1666
1667 must(verify(pubKeyPath, imgName, true, nil, "", true), t)
1668 }
1669
1670 func TestGetPublicKeyCustomOut(t *testing.T) {
1671 td := t.TempDir()
1672 keys, privKeyPath, _ := keypair(t, td)
1673 ctx := context.Background()
1674
1675 outFile := "output.pub"
1676 outPath := filepath.Join(td, outFile)
1677 outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600)
1678 must(err, t)
1679
1680 pk := publickey.Pkopts{
1681 KeyRef: privKeyPath,
1682 }
1683 must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t)
1684
1685 output, err := os.ReadFile(outPath)
1686 must(err, t)
1687 equals(keys.PublicBytes, output, t)
1688 }
1689
1690
1691
1692
1693
1694
1695
1696 func TestInvalidBundle(t *testing.T) {
1697 td := t.TempDir()
1698 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1699 if err != nil {
1700 t.Fatal(err)
1701 }
1702
1703 regName, stop := reg(t)
1704 defer stop()
1705
1706 img1 := path.Join(regName, "cosign-e2e")
1707
1708 imgRef, _, cleanup := mkimage(t, img1)
1709 defer cleanup()
1710
1711 _, privKeyPath, pubKeyPath := keypair(t, td)
1712
1713 ctx := context.Background()
1714
1715
1716
1717 remoteOpts := ociremote.WithRemoteOptions(registryClientOpts(ctx)...)
1718 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL}
1719 so := options.SignOptions{
1720 Upload: true,
1721 TlogUpload: true,
1722 SkipConfirmation: true,
1723 }
1724 must(sign.SignCmd(ro, ko, so, []string{img1}), t)
1725
1726 must(verify(pubKeyPath, img1, true, nil, "", false), t)
1727
1728 si, err := ociremote.SignedImage(imgRef, remoteOpts)
1729 must(err, t)
1730 imgSigs, err := si.Signatures()
1731 must(err, t)
1732 sigs, err := imgSigs.Get()
1733 must(err, t)
1734 if l := len(sigs); l != 1 {
1735 t.Error("expected one signature")
1736 }
1737 bund, err := sigs[0].Bundle()
1738 must(err, t)
1739 if bund == nil {
1740 t.Fail()
1741 }
1742
1743
1744
1745 img2 := path.Join(regName, "unrelated")
1746 imgRef2, _, cleanup := mkimage(t, img2)
1747 defer cleanup()
1748 so = options.SignOptions{
1749 Upload: true,
1750 TlogUpload: false,
1751 }
1752 must(sign.SignCmd(ro, ko, so, []string{img2}), t)
1753 must(verify(pubKeyPath, img2, true, nil, "", true), t)
1754
1755 si2, err := ociremote.SignedEntity(imgRef2, remoteOpts)
1756 must(err, t)
1757 sigs2, err := si2.Signatures()
1758 must(err, t)
1759 gottenSigs2, err := sigs2.Get()
1760 must(err, t)
1761 if len(gottenSigs2) != 1 {
1762 t.Fatal("there should be one signature")
1763 }
1764 sigsTag, err := ociremote.SignatureTag(imgRef2)
1765 if err != nil {
1766 t.Fatal(err)
1767 }
1768
1769
1770
1771
1772 if err := remote.Delete(sigsTag); err != nil {
1773 t.Fatal(err)
1774 }
1775 mustErr(verify(pubKeyPath, img2, true, nil, "", false), t)
1776
1777 newSig, err := mutate.Signature(gottenSigs2[0], mutate.WithBundle(bund))
1778 must(err, t)
1779 si2, err = ociremote.SignedEntity(imgRef2, remoteOpts)
1780 must(err, t)
1781 newImage, err := mutate.AttachSignatureToEntity(si2, newSig)
1782 must(err, t)
1783 if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil {
1784 t.Fatal(err)
1785 }
1786
1787
1788 cmd := cliverify.VerifyCommand{
1789 KeyRef: pubKeyPath,
1790 RekorURL: rekorURL,
1791 CheckClaims: true,
1792 HashAlgorithm: crypto.SHA256,
1793 MaxWorkers: 10,
1794 }
1795 args := []string{img2}
1796 mustErr(cmd.Exec(context.Background(), args), t)
1797 }
1798
1799 func TestAttestBlobSignVerify(t *testing.T) {
1800 blob := "someblob"
1801 predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
1802 predicateType := "slsaprovenance"
1803
1804 td1 := t.TempDir()
1805 t.Cleanup(func() {
1806 os.RemoveAll(td1)
1807 })
1808
1809 bp := filepath.Join(td1, blob)
1810 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil {
1811 t.Fatal(err)
1812 }
1813
1814 anotherBlob := filepath.Join(td1, "another-blob")
1815 if err := os.WriteFile(anotherBlob, []byte("another-blob"), 0644); err != nil {
1816 t.Fatal(err)
1817 }
1818
1819 predicatePath := filepath.Join(td1, "predicate")
1820 if err := os.WriteFile(predicatePath, []byte(predicate), 0644); err != nil {
1821 t.Fatal(err)
1822 }
1823
1824 outputSignature := filepath.Join(td1, "signature")
1825
1826 _, privKeyPath1, pubKeyPath1 := keypair(t, td1)
1827
1828 ctx := context.Background()
1829 ko := options.KeyOpts{
1830 KeyRef: pubKeyPath1,
1831 }
1832 blobVerifyAttestationCmd := cliverify.VerifyBlobAttestationCommand{
1833 KeyOpts: ko,
1834 SignaturePath: outputSignature,
1835 PredicateType: predicateType,
1836 IgnoreTlog: true,
1837 CheckClaims: true,
1838 }
1839
1840 mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t)
1841
1842
1843 ko = options.KeyOpts{
1844 KeyRef: privKeyPath1,
1845 PassFunc: passFunc,
1846 }
1847 attestBlobCmd := attest.AttestBlobCommand{
1848 KeyOpts: ko,
1849 PredicatePath: predicatePath,
1850 PredicateType: predicateType,
1851 OutputSignature: outputSignature,
1852 RekorEntryType: "dsse",
1853 }
1854 must(attestBlobCmd.Exec(ctx, bp), t)
1855
1856
1857 must(blobVerifyAttestationCmd.Exec(ctx, bp), t)
1858
1859
1860 blobVerifyAttestationCmd.PredicateType = "custom"
1861 mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t)
1862
1863
1864 blobVerifyAttestationCmd.PredicateType = predicateType
1865 mustErr(blobVerifyAttestationCmd.Exec(ctx, anotherBlob), t)
1866 }
1867
1868 func TestOffline(t *testing.T) {
1869 td := t.TempDir()
1870 err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td)
1871 if err != nil {
1872 t.Fatal(err)
1873 }
1874
1875 regName, stop := reg(t)
1876 defer stop()
1877
1878 img1 := path.Join(regName, "cosign-e2e")
1879
1880 imgRef, _, cleanup := mkimage(t, img1)
1881 defer cleanup()
1882
1883 _, privKeyPath, pubKeyPath := keypair(t, td)
1884
1885 ctx := context.Background()
1886
1887
1888 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL}
1889 so := options.SignOptions{
1890 Upload: true,
1891 TlogUpload: true,
1892 SkipConfirmation: true,
1893 }
1894 must(sign.SignCmd(ro, ko, so, []string{img1}), t)
1895
1896 must(verify(pubKeyPath, img1, true, nil, "", false), t)
1897 verifyCmd := &cliverify.VerifyCommand{
1898 KeyRef: pubKeyPath,
1899 RekorURL: "notreal",
1900 Offline: true,
1901 CheckClaims: true,
1902 MaxWorkers: 10,
1903 }
1904 must(verifyCmd.Exec(ctx, []string{img1}), t)
1905
1906
1907 si, err := ociremote.SignedEntity(imgRef)
1908 must(err, t)
1909 sigs, err := si.Signatures()
1910 must(err, t)
1911 gottenSigs, err := sigs.Get()
1912 must(err, t)
1913
1914 fakeBundle := &bundle.RekorBundle{
1915 SignedEntryTimestamp: []byte(""),
1916 Payload: bundle.RekorPayload{
1917 Body: "",
1918 },
1919 }
1920 newSig, err := mutate.Signature(gottenSigs[0], mutate.WithBundle(fakeBundle))
1921 must(err, t)
1922
1923 sigsTag, err := ociremote.SignatureTag(imgRef)
1924 if err := remote.Delete(sigsTag); err != nil {
1925 t.Fatal(err)
1926 }
1927
1928 si, err = ociremote.SignedEntity(imgRef)
1929 must(err, t)
1930 newImage, err := mutate.AttachSignatureToEntity(si, newSig)
1931 must(err, t)
1932
1933 mustErr(verify(pubKeyPath, img1, true, nil, "", false), t)
1934 if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil {
1935 t.Fatal(err)
1936 }
1937
1938
1939 mustErr(verifyCmd.Exec(ctx, []string{img1}), t)
1940 }
1941
1942 func TestDockerfileVerify(t *testing.T) {
1943 td := t.TempDir()
1944
1945
1946 err := setLocalEnv(t, td)
1947 if err != nil {
1948 t.Fatal(err)
1949 }
1950
1951
1952 err = fulcioroots.ReInit()
1953 if err != nil {
1954 t.Fatal(err)
1955 }
1956
1957 identityToken, err := getOIDCToken()
1958 if err != nil {
1959 t.Fatal(err)
1960 }
1961
1962
1963 repo, stop := reg(t)
1964 defer stop()
1965 signedImg1 := path.Join(repo, "cosign-e2e-dockerfile-signed1")
1966 _, _, cleanup1 := mkimage(t, signedImg1)
1967 defer cleanup1()
1968 signedImg2 := path.Join(repo, "cosign-e2e-dockerfile-signed2")
1969 _, _, cleanup2 := mkimage(t, signedImg2)
1970 defer cleanup2()
1971 unsignedImg := path.Join(repo, "cosign-e2e-dockerfile-unsigned")
1972 _, _, cleanupUnsigned := mkimage(t, unsignedImg)
1973 defer cleanupUnsigned()
1974
1975
1976 ko := options.KeyOpts{
1977 FulcioURL: fulcioURL,
1978 RekorURL: rekorURL,
1979 IDToken: identityToken,
1980 SkipConfirmation: true,
1981 }
1982 so := options.SignOptions{
1983 Upload: true,
1984 TlogUpload: true,
1985 SkipConfirmation: true,
1986 }
1987 ctx := context.Background()
1988 must(sign.SignCmd(ro, ko, so, []string{signedImg1}), t)
1989 must(sign.SignCmd(ro, ko, so, []string{signedImg2}), t)
1990
1991
1992 singleStageDockerfileContents := fmt.Sprintf(`
1993 FROM %s
1994 `, signedImg1)
1995 singleStageDockerfile := mkfile(singleStageDockerfileContents, td, t)
1996
1997 unsignedBuildStageDockerfileContents := fmt.Sprintf(`
1998 FROM %s
1999
2000 FROM %s
2001
2002 FROM %s
2003 `, signedImg1, unsignedImg, signedImg2)
2004 unsignedBuildStageDockerfile := mkfile(unsignedBuildStageDockerfileContents, td, t)
2005
2006 fromAsDockerfileContents := fmt.Sprintf(`
2007 FROM --platform=linux/amd64 %s AS base
2008 `, signedImg1)
2009 fromAsDockerfile := mkfile(fromAsDockerfileContents, td, t)
2010
2011 withArgDockerfileContents := `
2012 ARG test_image
2013
2014 FROM ${test_image}
2015 `
2016 withArgDockerfile := mkfile(withArgDockerfileContents, td, t)
2017
2018 withLowercaseDockerfileContents := fmt.Sprintf(`
2019 from %s
2020 `, signedImg1)
2021 withLowercaseDockerfile := mkfile(withLowercaseDockerfileContents, td, t)
2022
2023 issuer := os.Getenv("OIDC_URL")
2024
2025 tests := []struct {
2026 name string
2027 dockerfile string
2028 baseOnly bool
2029 env map[string]string
2030 wantErr bool
2031 }{
2032 {
2033 name: "verify single stage",
2034 dockerfile: singleStageDockerfile,
2035 },
2036 {
2037 name: "verify unsigned build stage",
2038 dockerfile: unsignedBuildStageDockerfile,
2039 wantErr: true,
2040 },
2041 {
2042 name: "verify base image only",
2043 dockerfile: unsignedBuildStageDockerfile,
2044 baseOnly: true,
2045 },
2046 {
2047 name: "verify from as",
2048 dockerfile: fromAsDockerfile,
2049 },
2050 {
2051 name: "verify with arg",
2052 dockerfile: withArgDockerfile,
2053 env: map[string]string{"test_image": signedImg1},
2054 },
2055 {
2056 name: "verify image exists but is unsigned",
2057 dockerfile: withArgDockerfile,
2058 env: map[string]string{"test_image": unsignedImg},
2059 wantErr: true,
2060 },
2061 {
2062 name: "verify with lowercase",
2063 dockerfile: withLowercaseDockerfile,
2064 },
2065 }
2066 for _, test := range tests {
2067 t.Run(test.name, func(t *testing.T) {
2068 cmd := dockerfile.VerifyDockerfileCommand{
2069 VerifyCommand: cliverify.VerifyCommand{
2070 CertVerifyOptions: options.CertVerifyOptions{
2071 CertOidcIssuer: issuer,
2072 CertIdentity: certID,
2073 },
2074 RekorURL: rekorURL,
2075 },
2076 BaseOnly: test.baseOnly,
2077 }
2078 args := []string{test.dockerfile}
2079 for k, v := range test.env {
2080 t.Setenv(k, v)
2081 }
2082 if test.wantErr {
2083 mustErr(cmd.Exec(ctx, args), t)
2084 } else {
2085 must(cmd.Exec(ctx, args), t)
2086 }
2087 })
2088 }
2089 }
2090
2091 func TestManifestVerify(t *testing.T) {
2092 td := t.TempDir()
2093
2094
2095 err := setLocalEnv(t, td)
2096 if err != nil {
2097 t.Fatal(err)
2098 }
2099
2100
2101 err = fulcioroots.ReInit()
2102 if err != nil {
2103 t.Fatal(err)
2104 }
2105
2106 identityToken, err := getOIDCToken()
2107 if err != nil {
2108 t.Fatal(err)
2109 }
2110
2111
2112 repo, stop := reg(t)
2113 defer stop()
2114 signedImg := path.Join(repo, "cosign-e2e-manifest-signed")
2115 _, _, cleanup := mkimage(t, signedImg)
2116 defer cleanup()
2117 unsignedImg := path.Join(repo, "cosign-e2e-manifest-unsigned")
2118 _, _, cleanupUnsigned := mkimage(t, unsignedImg)
2119 defer cleanupUnsigned()
2120
2121
2122 ko := options.KeyOpts{
2123 FulcioURL: fulcioURL,
2124 RekorURL: rekorURL,
2125 IDToken: identityToken,
2126 SkipConfirmation: true,
2127 }
2128 so := options.SignOptions{
2129 Upload: true,
2130 TlogUpload: true,
2131 SkipConfirmation: true,
2132 }
2133 ctx := context.Background()
2134 must(sign.SignCmd(ro, ko, so, []string{signedImg}), t)
2135
2136
2137 manifestTemplate := `
2138 apiVersion: v1
2139 kind: Pod
2140 metadata:
2141 name: single-pod
2142 spec:
2143 containers:
2144 - name: %s
2145 image: %s
2146 `
2147 signedManifestContents := fmt.Sprintf(manifestTemplate, "signed-img", signedImg)
2148 signedManifest := mkfileWithExt(signedManifestContents, td, ".yaml", t)
2149 unsignedManifestContents := fmt.Sprintf(manifestTemplate, "unsigned-img", unsignedImg)
2150 unsignedManifest := mkfileWithExt(unsignedManifestContents, td, ".yaml", t)
2151
2152 issuer := os.Getenv("OIDC_URL")
2153
2154 tests := []struct {
2155 name string
2156 manifest string
2157 wantErr bool
2158 }{
2159 {
2160 name: "signed manifest",
2161 manifest: signedManifest,
2162 },
2163 {
2164 name: "unsigned manifest",
2165 manifest: unsignedManifest,
2166 wantErr: true,
2167 },
2168 }
2169 for _, test := range tests {
2170 t.Run(test.name, func(t *testing.T) {
2171 cmd := manifest.VerifyManifestCommand{
2172 VerifyCommand: cliverify.VerifyCommand{
2173 CertVerifyOptions: options.CertVerifyOptions{
2174 CertOidcIssuer: issuer,
2175 CertIdentity: certID,
2176 },
2177 RekorURL: rekorURL,
2178 },
2179 }
2180 args := []string{test.manifest}
2181 if test.wantErr {
2182 mustErr(cmd.Exec(ctx, args), t)
2183 } else {
2184 must(cmd.Exec(ctx, args), t)
2185 }
2186 })
2187 }
2188 }
2189
2190
2191 func getOIDCToken() (string, error) {
2192 issuer := os.Getenv("OIDC_URL")
2193 resp, err := http.Get(issuer + "/token")
2194 if err != nil {
2195 return "", err
2196 }
2197 defer resp.Body.Close()
2198 body, err := io.ReadAll(resp.Body)
2199 if err != nil {
2200 return "", err
2201 }
2202 return string(body), nil
2203 }
2204
2205 func setLocalEnv(t *testing.T, dir string) error {
2206
2207 home, err := os.UserHomeDir()
2208 if err != nil {
2209 return fmt.Errorf("error getting home directory: %w", err)
2210 }
2211 t.Setenv(env.VariableSigstoreCTLogPublicKeyFile.String(), path.Join(home, "fulcio/config/ctfe/pubkey.pem"))
2212 err = downloadAndSetEnv(t, fulcioURL+"/api/v1/rootCert", env.VariableSigstoreRootFile.String(), dir)
2213 if err != nil {
2214 return fmt.Errorf("error setting %s env var: %w", env.VariableSigstoreRootFile.String(), err)
2215 }
2216 err = downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), dir)
2217 if err != nil {
2218 return fmt.Errorf("error setting %s env var: %w", env.VariableSigstoreRekorPublicKey.String(), err)
2219 }
2220 return nil
2221 }
2222
2223 func downloadAndSetEnv(t *testing.T, url, envVar, dir string) error {
2224 resp, err := http.Get(url)
2225 if err != nil {
2226 return fmt.Errorf("error downloading file: %w", err)
2227 }
2228 defer resp.Body.Close()
2229 f, err := os.CreateTemp(dir, "")
2230 if err != nil {
2231 return fmt.Errorf("error creating temp file: %w", err)
2232 }
2233 defer f.Close()
2234 _, err = io.Copy(f, resp.Body)
2235 if err != nil {
2236 return fmt.Errorf("error writing to file: %w", err)
2237 }
2238 t.Setenv(envVar, f.Name())
2239 return nil
2240 }
2241
View as plain text