github.com/venafi-iw/cosign@v1.3.4/test/e2e_test.go (about) 1 // 2 // Copyright 2021 The Sigstore Authors. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 //go:build e2e 17 // +build e2e 18 19 package test 20 21 import ( 22 "bytes" 23 "context" 24 "crypto" 25 "encoding/base64" 26 "encoding/json" 27 "fmt" 28 "io" 29 "net/http/httptest" 30 "net/url" 31 "os" 32 "path" 33 "path/filepath" 34 "testing" 35 "time" 36 ftime "time" 37 38 "github.com/google/go-cmp/cmp" 39 "github.com/google/go-containerregistry/pkg/authn" 40 "github.com/google/go-containerregistry/pkg/name" 41 "github.com/google/go-containerregistry/pkg/registry" 42 "github.com/google/go-containerregistry/pkg/v1/random" 43 "github.com/google/go-containerregistry/pkg/v1/remote" 44 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 45 46 "github.com/sigstore/cosign/cmd/cosign/cli" 47 "github.com/sigstore/cosign/cmd/cosign/cli/attach" 48 "github.com/sigstore/cosign/cmd/cosign/cli/attest" 49 "github.com/sigstore/cosign/cmd/cosign/cli/download" 50 "github.com/sigstore/cosign/cmd/cosign/cli/generate" 51 "github.com/sigstore/cosign/cmd/cosign/cli/options" 52 "github.com/sigstore/cosign/cmd/cosign/cli/publickey" 53 "github.com/sigstore/cosign/cmd/cosign/cli/sign" 54 "github.com/sigstore/cosign/cmd/cosign/cli/upload" 55 cliverify "github.com/sigstore/cosign/cmd/cosign/cli/verify" 56 "github.com/sigstore/cosign/pkg/cosign" 57 "github.com/sigstore/cosign/pkg/cosign/kubernetes" 58 cremote "github.com/sigstore/cosign/pkg/cosign/remote" 59 ociremote "github.com/sigstore/cosign/pkg/oci/remote" 60 "github.com/sigstore/cosign/pkg/sget" 61 sigs "github.com/sigstore/cosign/pkg/signature" 62 "github.com/sigstore/sigstore/pkg/signature/payload" 63 ) 64 65 const ( 66 serverEnv = "REKOR_SERVER" 67 rekorURL = "https://rekor.sigstore.dev" 68 ) 69 70 var keyPass = []byte("hello") 71 72 var passFunc = func(_ bool) ([]byte, error) { 73 return keyPass, nil 74 } 75 76 var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string) error { 77 cmd := cliverify.VerifyCommand{ 78 KeyRef: keyRef, 79 RekorURL: rekorURL, 80 CheckClaims: checkClaims, 81 Annotations: sigs.AnnotationsMap{Annotations: annotations}, 82 Attachment: attachment, 83 HashAlgorithm: crypto.SHA256, 84 } 85 86 args := []string{imageRef} 87 88 return cmd.Exec(context.Background(), args) 89 } 90 91 // Used to verify local images stored on disk 92 var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error { 93 cmd := cliverify.VerifyCommand{ 94 KeyRef: keyRef, 95 CheckClaims: checkClaims, 96 Annotations: sigs.AnnotationsMap{Annotations: annotations}, 97 Attachment: attachment, 98 HashAlgorithm: crypto.SHA256, 99 LocalImage: true, 100 } 101 102 args := []string{path} 103 104 return cmd.Exec(context.Background(), args) 105 } 106 107 func TestSignVerify(t *testing.T) { 108 repo, stop := reg(t) 109 defer stop() 110 td := t.TempDir() 111 112 imgName := path.Join(repo, "cosign-e2e") 113 114 _, _, cleanup := mkimage(t, imgName) 115 defer cleanup() 116 117 _, privKeyPath, pubKeyPath := keypair(t, td) 118 119 ctx := context.Background() 120 // Verify should fail at first 121 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 122 // So should download 123 mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 124 125 // Now sign the image 126 ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 127 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 128 129 // Now verify and download should work! 130 must(verify(pubKeyPath, imgName, true, nil, ""), t) 131 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 132 133 // Look for a specific annotation 134 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t) 135 136 // Sign the image with an annotation 137 annotations := map[string]interface{}{"foo": "bar"} 138 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, annotations, []string{imgName}, "", true, "", "", "", false, false, ""), t) 139 140 // It should match this time. 141 must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t) 142 143 // But two doesn't work 144 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, ""), t) 145 } 146 147 func TestSignVerifyClean(t *testing.T) { 148 repo, stop := reg(t) 149 defer stop() 150 td := t.TempDir() 151 152 imgName := path.Join(repo, "cosign-e2e") 153 154 _, _, _ = mkimage(t, imgName) 155 156 _, privKeyPath, pubKeyPath := keypair(t, td) 157 158 ctx := context.Background() 159 160 // Now sign the image 161 ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 162 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 163 164 // Now verify and download should work! 165 must(verify(pubKeyPath, imgName, true, nil, ""), t) 166 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 167 168 // Now clean signature from the given image 169 must(cli.CleanCmd(ctx, options.RegistryOptions{}, imgName), t) 170 171 // It doesn't work 172 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 173 } 174 175 func TestAttestVerify(t *testing.T) { 176 repo, stop := reg(t) 177 defer stop() 178 td := t.TempDir() 179 180 imgName := path.Join(repo, "cosign-attest-e2e") 181 182 _, _, cleanup := mkimage(t, imgName) 183 defer cleanup() 184 185 _, privKeyPath, pubKeyPath := keypair(t, td) 186 187 ctx := context.Background() 188 189 // Verify should fail at first 190 verifyAttestation := cliverify.VerifyAttestationCommand{ 191 KeyRef: pubKeyPath, 192 } 193 194 // Fail case when using without type and policy flag 195 mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) 196 197 slsaAttestation := `{ "builder": { "id": "2" }, "recipe": {} }` 198 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") 199 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { 200 t.Fatal(err) 201 } 202 203 // Now attest the image 204 ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 205 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false, 206 "custom", false, ftime.Duration(30*time.Second)), t) 207 208 // Use cue to verify attestation 209 policyPath := filepath.Join(td, "policy.cue") 210 verifyAttestation.PredicateType = "slsaprovenance" 211 verifyAttestation.Policies = []string{policyPath} 212 213 // Fail case 214 cuePolicy := `builder: id: "1"` 215 if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil { 216 t.Fatal(err) 217 } 218 219 // Success case 220 cuePolicy = `builder: id: "2"` 221 if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil { 222 t.Fatal(err) 223 } 224 must(verifyAttestation.Exec(ctx, []string{imgName}), t) 225 226 // Look for a specific annotation 227 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t) 228 } 229 230 func TestBundle(t *testing.T) { 231 // turn on the tlog 232 defer setenv(t, options.ExperimentalEnv, "1")() 233 234 repo, stop := reg(t) 235 defer stop() 236 td := t.TempDir() 237 238 imgName := path.Join(repo, "cosign-e2e") 239 240 _, _, cleanup := mkimage(t, imgName) 241 defer cleanup() 242 243 _, privKeyPath, pubKeyPath := keypair(t, td) 244 245 ctx := context.Background() 246 247 ko := sign.KeyOpts{ 248 KeyRef: privKeyPath, 249 PassFunc: passFunc, 250 RekorURL: rekorURL, 251 } 252 253 // Sign the image 254 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 255 // Make sure verify works 256 must(verify(pubKeyPath, imgName, true, nil, ""), t) 257 258 // Make sure offline verification works with bundling 259 // use rekor prod since we have hardcoded the public key 260 os.Setenv(serverEnv, "notreal") 261 must(verify(pubKeyPath, imgName, true, nil, ""), t) 262 } 263 264 func TestDuplicateSign(t *testing.T) { 265 repo, stop := reg(t) 266 defer stop() 267 td := t.TempDir() 268 269 imgName := path.Join(repo, "cosign-e2e") 270 271 ref, _, cleanup := mkimage(t, imgName) 272 defer cleanup() 273 274 _, privKeyPath, pubKeyPath := keypair(t, td) 275 276 ctx := context.Background() 277 // Verify should fail at first 278 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 279 // So should download 280 mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 281 282 // Now sign the image 283 ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 284 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 285 286 // Now verify and download should work! 287 must(verify(pubKeyPath, imgName, true, nil, ""), t) 288 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 289 290 // Signing again should work just fine... 291 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 292 293 se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...)) 294 must(err, t) 295 sigs, err := se.Signatures() 296 must(err, t) 297 signatures, err := sigs.Get() 298 must(err, t) 299 300 if len(signatures) > 1 { 301 t.Errorf("expected there to only be one signature, got %v", signatures) 302 } 303 } 304 305 func TestKeyURLVerify(t *testing.T) { 306 // TODO: re-enable once distroless images are being signed by the new client 307 t.Skip() 308 // Verify that an image can be verified via key url 309 keyRef := "https://raw.githubusercontent.com/GoogleContainerTools/distroless/main/cosign.pub" 310 img := "gcr.io/distroless/base:latest" 311 312 must(verify(keyRef, img, true, nil, ""), t) 313 } 314 315 func TestGenerateKeyPairEnvVar(t *testing.T) { 316 defer setenv(t, "COSIGN_PASSWORD", "foo")() 317 keys, err := cosign.GenerateKeyPair(generate.GetPass) 318 if err != nil { 319 t.Fatal(err) 320 } 321 if _, err := cosign.LoadECDSAPrivateKey(keys.PrivateBytes, []byte("foo")); err != nil { 322 t.Fatal(err) 323 } 324 } 325 326 func TestGenerateKeyPairK8s(t *testing.T) { 327 td := t.TempDir() 328 wd, err := os.Getwd() 329 if err != nil { 330 t.Fatal(err) 331 } 332 if err := os.Chdir(td); err != nil { 333 t.Fatal(err) 334 } 335 defer func() { 336 os.Chdir(wd) 337 }() 338 password := "foo" 339 defer setenv(t, "COSIGN_PASSWORD", password)() 340 ctx := context.Background() 341 name := "cosign-secret" 342 namespace := "default" 343 if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("k8s://%s/%s", namespace, name), generate.GetPass); err != nil { 344 t.Fatal(err) 345 } 346 // make sure the secret actually exists 347 client, err := kubernetes.Client() 348 if err != nil { 349 t.Fatal(err) 350 } 351 s, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) 352 if err != nil { 353 t.Fatal(err) 354 } 355 if v, ok := s.Data["cosign.password"]; !ok || string(v) != password { 356 t.Fatalf("password is incorrect, got %v expected %v", v, "foo") 357 } 358 } 359 360 func TestMultipleSignatures(t *testing.T) { 361 repo, stop := reg(t) 362 defer stop() 363 364 td1 := t.TempDir() 365 td2 := t.TempDir() 366 367 imgName := path.Join(repo, "cosign-e2e") 368 369 _, _, cleanup := mkimage(t, imgName) 370 defer cleanup() 371 372 _, priv1, pub1 := keypair(t, td1) 373 _, priv2, pub2 := keypair(t, td2) 374 375 ctx := context.Background() 376 377 // Verify should fail at first for both keys 378 mustErr(verify(pub1, imgName, true, nil, ""), t) 379 mustErr(verify(pub2, imgName, true, nil, ""), t) 380 381 // Now sign the image with one key 382 ko := sign.KeyOpts{KeyRef: priv1, PassFunc: passFunc} 383 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 384 // Now verify should work with that one, but not the other 385 must(verify(pub1, imgName, true, nil, ""), t) 386 mustErr(verify(pub2, imgName, true, nil, ""), t) 387 388 // Now sign with the other key too 389 ko.KeyRef = priv2 390 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 391 392 // Now verify should work with both 393 must(verify(pub1, imgName, true, nil, ""), t) 394 must(verify(pub2, imgName, true, nil, ""), t) 395 } 396 397 func TestSignBlob(t *testing.T) { 398 blob := "someblob" 399 td1 := t.TempDir() 400 td2 := t.TempDir() 401 t.Cleanup(func() { 402 os.RemoveAll(td1) 403 os.RemoveAll(td2) 404 }) 405 bp := filepath.Join(td1, blob) 406 407 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { 408 t.Fatal(err) 409 } 410 411 _, privKeyPath1, pubKeyPath1 := keypair(t, td1) 412 _, _, pubKeyPath2 := keypair(t, td2) 413 414 ctx := context.Background() 415 416 ko1 := sign.KeyOpts{ 417 KeyRef: pubKeyPath1, 418 } 419 ko2 := sign.KeyOpts{ 420 KeyRef: pubKeyPath2, 421 } 422 // Verify should fail on a bad input 423 mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "", "badsig", blob), t) 424 mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "", "badsig", blob), t) 425 426 // Now sign the blob with one key 427 ko := sign.KeyOpts{ 428 KeyRef: privKeyPath1, 429 PassFunc: passFunc, 430 } 431 sig, err := sign.SignBlobCmd(ctx, ko, options.RegistryOptions{}, bp, true, "", "", time.Duration(30*time.Second)) 432 if err != nil { 433 t.Fatal(err) 434 } 435 // Now verify should work with that one, but not the other 436 must(cliverify.VerifyBlobCmd(ctx, ko1, "", string(sig), bp), t) 437 mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "", string(sig), bp), t) 438 } 439 440 func TestGenerate(t *testing.T) { 441 repo, stop := reg(t) 442 defer stop() 443 444 imgName := path.Join(repo, "cosign-e2e") 445 _, desc, cleanup := mkimage(t, imgName) 446 defer cleanup() 447 448 // Generate the payload for the image, and check the digest. 449 b := bytes.Buffer{} 450 must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) 451 ss := payload.SimpleContainerImage{} 452 must(json.Unmarshal(b.Bytes(), &ss), t) 453 454 equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) 455 456 // Now try with some annotations. 457 b.Reset() 458 a := map[string]interface{}{"foo": "bar"} 459 must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, a, &b), t) 460 must(json.Unmarshal(b.Bytes(), &ss), t) 461 462 equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) 463 equals(ss.Optional["foo"], "bar", t) 464 } 465 466 func keypair(t *testing.T, td string) (*cosign.Keys, string, string) { 467 wd, err := os.Getwd() 468 if err != nil { 469 t.Fatal(err) 470 } 471 if err := os.Chdir(td); err != nil { 472 t.Fatal(err) 473 } 474 defer func() { 475 os.Chdir(wd) 476 }() 477 keys, err := cosign.GenerateKeyPair(passFunc) 478 if err != nil { 479 t.Fatal(err) 480 } 481 482 privKeyPath := filepath.Join(td, "cosign.key") 483 if err := os.WriteFile(privKeyPath, keys.PrivateBytes, 0600); err != nil { 484 t.Fatal(err) 485 } 486 487 pubKeyPath := filepath.Join(td, "cosign.pub") 488 if err := os.WriteFile(pubKeyPath, keys.PublicBytes, 0600); err != nil { 489 t.Fatal(err) 490 } 491 return keys, privKeyPath, pubKeyPath 492 } 493 494 func TestUploadDownload(t *testing.T) { 495 repo, stop := reg(t) 496 defer stop() 497 td := t.TempDir() 498 ctx := context.Background() 499 500 testCases := map[string]struct { 501 signature string 502 signatureType attach.SignatureArgType 503 expectedErr bool 504 }{ 505 "file containing signature": { 506 signature: "testsignaturefile", 507 signatureType: attach.FileSignature, 508 expectedErr: false, 509 }, 510 "raw signature as argument": { 511 signature: "testsignatureraw", 512 signatureType: attach.RawSignature, 513 expectedErr: false, 514 }, 515 "empty signature as argument": { 516 signature: "", 517 signatureType: attach.RawSignature, 518 expectedErr: true, 519 }, 520 } 521 522 imgName := path.Join(repo, "cosign-e2e") 523 for testName, testCase := range testCases { 524 t.Run(testName, func(t *testing.T) { 525 ref, _, cleanup := mkimage(t, imgName) 526 payload := "testpayload" 527 payloadPath := mkfile(payload, td, t) 528 signature := base64.StdEncoding.EncodeToString([]byte(testCase.signature)) 529 530 var sigRef string 531 if testCase.signatureType == attach.FileSignature { 532 sigRef = mkfile(signature, td, t) 533 } else { 534 sigRef = signature 535 } 536 537 // Upload it! 538 err := attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadPath, imgName) 539 if testCase.expectedErr { 540 mustErr(err, t) 541 } else { 542 must(err, t) 543 } 544 545 // Now download it! 546 se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...)) 547 must(err, t) 548 sigs, err := se.Signatures() 549 must(err, t) 550 signatures, err := sigs.Get() 551 must(err, t) 552 553 if testCase.expectedErr { 554 if len(signatures) != 0 { 555 t.Fatalf("unexpected signatures %d, wanted 0", len(signatures)) 556 } 557 } else { 558 if len(signatures) != 1 { 559 t.Fatalf("unexpected signatures %d, wanted 1", len(signatures)) 560 } 561 562 if b64sig, err := signatures[0].Base64Signature(); err != nil { 563 t.Fatalf("Base64Signature() = %v", err) 564 } else if diff := cmp.Diff(b64sig, signature); diff != "" { 565 t.Error(diff) 566 } 567 568 if p, err := signatures[0].Payload(); err != nil { 569 t.Fatalf("Payload() = %v", err) 570 } else if diff := cmp.Diff(p, []byte(payload)); diff != "" { 571 t.Error(diff) 572 } 573 } 574 575 // Now delete it! 576 cleanup() 577 }) 578 } 579 } 580 581 func TestUploadBlob(t *testing.T) { 582 repo, stop := reg(t) 583 defer stop() 584 td := t.TempDir() 585 ctx := context.Background() 586 587 imgName := path.Join(repo, "/cosign-upload-e2e") 588 payload := "testpayload" 589 payloadPath := mkfile(payload, td, t) 590 591 // Upload it! 592 files := []cremote.File{cremote.FileFromFlag(payloadPath)} 593 must(upload.BlobCmd(ctx, options.RegistryOptions{}, files, "", imgName), t) 594 595 // Check it 596 ref, err := name.ParseReference(imgName) 597 if err != nil { 598 t.Fatal(err) 599 } 600 601 // Now download it with sget (this should fail by tag) 602 if err := sget.New(imgName, "", os.Stdout).Do(ctx); err == nil { 603 t.Error("expected download to fail") 604 } 605 606 img, err := remote.Image(ref) 607 if err != nil { 608 t.Fatal(err) 609 } 610 dgst, err := img.Digest() 611 if err != nil { 612 t.Fatal(err) 613 } 614 615 result := &bytes.Buffer{} 616 617 // But pass by digest 618 if err := sget.New(imgName+"@"+dgst.String(), "", result).Do(ctx); err != nil { 619 t.Fatal(err) 620 } 621 b, err := io.ReadAll(result) 622 if err != nil { 623 t.Fatal(err) 624 } 625 if string(b) != payload { 626 t.Errorf("expected contents to be %s, got %s", payload, string(b)) 627 } 628 } 629 630 func TestSaveLoad(t *testing.T) { 631 tests := []struct { 632 description string 633 getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) 634 }{ 635 { 636 description: "save and load an image", 637 getSignedEntity: mkimage, 638 }, 639 { 640 description: "save and load an image index", 641 getSignedEntity: mkimageindex, 642 }, 643 } 644 for i, test := range tests { 645 t.Run(test.description, func(t *testing.T) { 646 repo, stop := reg(t) 647 defer stop() 648 keysDir := t.TempDir() 649 650 imgName := path.Join(repo, fmt.Sprintf("save-load-%d", i)) 651 652 _, _, cleanup := test.getSignedEntity(t, imgName) 653 defer cleanup() 654 655 _, privKeyPath, pubKeyPath := keypair(t, keysDir) 656 657 ctx := context.Background() 658 // Now sign the image and verify it 659 ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 660 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 661 must(verify(pubKeyPath, imgName, true, nil, ""), t) 662 663 // save the image to a temp dir 664 imageDir := t.TempDir() 665 must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) 666 667 // verify the local image using a local key 668 must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t) 669 670 // load the image from the temp dir into a new image and verify the new image 671 imgName2 := path.Join(repo, fmt.Sprintf("save-load-%d-2", i)) 672 must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) 673 must(verify(pubKeyPath, imgName2, true, nil, ""), t) 674 }) 675 } 676 } 677 678 func TestSaveLoadAttestation(t *testing.T) { 679 repo, stop := reg(t) 680 defer stop() 681 td := t.TempDir() 682 683 imgName := path.Join(repo, "save-load") 684 685 _, _, cleanup := mkimage(t, imgName) 686 defer cleanup() 687 688 _, privKeyPath, pubKeyPath := keypair(t, td) 689 690 ctx := context.Background() 691 // Now sign the image and verify it 692 ko := sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 693 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 694 must(verify(pubKeyPath, imgName, true, nil, ""), t) 695 696 // now, append an attestation to the image 697 slsaAttestation := `{ "builder": { "id": "2" }, "recipe": {} }` 698 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") 699 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { 700 t.Fatal(err) 701 } 702 703 // Now attest the image 704 ko = sign.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 705 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", false, slsaAttestationPath, false, 706 "custom", false, ftime.Duration(30*time.Second)), t) 707 708 // save the image to a temp dir 709 imageDir := t.TempDir() 710 must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) 711 712 // load the image from the temp dir into a new image and verify the new image 713 imgName2 := path.Join(repo, "save-load-2") 714 must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) 715 must(verify(pubKeyPath, imgName2, true, nil, ""), t) 716 // Use cue to verify attestation on the new image 717 policyPath := filepath.Join(td, "policy.cue") 718 verifyAttestation := cliverify.VerifyAttestationCommand{ 719 KeyRef: pubKeyPath, 720 } 721 verifyAttestation.PredicateType = "slsaprovenance" 722 verifyAttestation.Policies = []string{policyPath} 723 // Success case (remote) 724 cuePolicy := `builder: id: "2"` 725 if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil { 726 t.Fatal(err) 727 } 728 must(verifyAttestation.Exec(ctx, []string{imgName2}), t) 729 // Success case (local) 730 verifyAttestation.LocalImage = true 731 must(verifyAttestation.Exec(ctx, []string{imageDir}), t) 732 } 733 734 func TestAttachSBOM(t *testing.T) { 735 repo, stop := reg(t) 736 defer stop() 737 ctx := context.Background() 738 739 imgName := path.Join(repo, "sbom-image") 740 img, _, cleanup := mkimage(t, imgName) 741 defer cleanup() 742 743 out := bytes.Buffer{} 744 _, err := download.SBOMCmd(ctx, options.RegistryOptions{}, img.Name(), &out) 745 if err == nil { 746 t.Fatal("Expected error") 747 } 748 t.Log(out.String()) 749 out.Reset() 750 751 // Upload it! 752 must(attach.SBOMCmd(ctx, options.RegistryOptions{}, "./testdata/bom-go-mod.spdx", "spdx", imgName), t) 753 754 sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, imgName, &out) 755 if err != nil { 756 t.Fatal(err) 757 } 758 t.Log(out.String()) 759 if len(sboms) != 1 { 760 t.Fatalf("Expected one sbom, got %d", len(sboms)) 761 } 762 want, err := os.ReadFile("./testdata/bom-go-mod.spdx") 763 if err != nil { 764 t.Fatal(err) 765 } 766 if diff := cmp.Diff(string(want), sboms[0]); diff != "" { 767 t.Errorf("diff: %s", diff) 768 } 769 770 // Generate key pairs to sign the sbom 771 td1 := t.TempDir() 772 td2 := t.TempDir() 773 _, privKeyPath1, pubKeyPath1 := keypair(t, td1) 774 _, _, pubKeyPath2 := keypair(t, td2) 775 776 // Verify should fail on a bad input 777 mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom"), t) 778 mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom"), t) 779 780 // Now sign the sbom with one key 781 ko1 := sign.KeyOpts{KeyRef: privKeyPath1, PassFunc: passFunc} 782 must(sign.SignCmd(ctx, ko1, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, "sbom"), t) 783 784 // Now verify should work with that one, but not the other 785 must(verify(pubKeyPath1, imgName, true, nil, "sbom"), t) 786 mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom"), t) 787 } 788 789 func setenv(t *testing.T, k, v string) func() { 790 if err := os.Setenv(k, v); err != nil { 791 t.Fatalf("error setting env: %v", err) 792 } 793 return func() { 794 os.Unsetenv(k) 795 } 796 } 797 798 func TestTlog(t *testing.T) { 799 repo, stop := reg(t) 800 defer stop() 801 td := t.TempDir() 802 803 imgName := path.Join(repo, "cosign-e2e") 804 805 _, _, cleanup := mkimage(t, imgName) 806 defer cleanup() 807 808 _, privKeyPath, pubKeyPath := keypair(t, td) 809 810 ctx := context.Background() 811 // Verify should fail at first 812 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 813 814 // Now sign the image without the tlog 815 ko := sign.KeyOpts{ 816 KeyRef: privKeyPath, 817 PassFunc: passFunc, 818 RekorURL: rekorURL, 819 } 820 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 821 822 // Now verify should work! 823 must(verify(pubKeyPath, imgName, true, nil, ""), t) 824 825 // Now we turn on the tlog! 826 defer setenv(t, options.ExperimentalEnv, "1")() 827 828 // Verify shouldn't work since we haven't put anything in it yet. 829 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 830 831 // Sign again with the tlog env var on 832 must(sign.SignCmd(ctx, ko, options.RegistryOptions{}, nil, []string{imgName}, "", true, "", "", "", false, false, ""), t) 833 // And now verify works! 834 must(verify(pubKeyPath, imgName, true, nil, ""), t) 835 } 836 837 func TestGetPublicKeyCustomOut(t *testing.T) { 838 td := t.TempDir() 839 keys, privKeyPath, _ := keypair(t, td) 840 ctx := context.Background() 841 842 outFile := "output.pub" 843 outPath := filepath.Join(td, outFile) 844 outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600) 845 must(err, t) 846 847 pk := publickey.Pkopts{ 848 KeyRef: privKeyPath, 849 } 850 must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t) 851 852 output, err := os.ReadFile(outPath) 853 must(err, t) 854 equals(keys.PublicBytes, output, t) 855 } 856 857 func mkfile(contents, td string, t *testing.T) string { 858 f, err := os.CreateTemp(td, "") 859 if err != nil { 860 t.Fatal(err) 861 } 862 defer f.Close() 863 if _, err := f.Write([]byte(contents)); err != nil { 864 t.Fatal(err) 865 } 866 return f.Name() 867 } 868 869 func mkimage(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) { 870 ref, err := name.ParseReference(n, name.WeakValidation) 871 if err != nil { 872 t.Fatal(err) 873 } 874 img, err := random.Image(512, 5) 875 if err != nil { 876 t.Fatal(err) 877 } 878 879 regClientOpts := registryClientOpts(context.Background()) 880 881 if err := remote.Write(ref, img, regClientOpts...); err != nil { 882 t.Fatal(err) 883 } 884 885 remoteImage, err := remote.Get(ref, regClientOpts...) 886 if err != nil { 887 t.Fatal(err) 888 } 889 890 cleanup := func() { 891 _ = remote.Delete(ref, regClientOpts...) 892 ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteImage.Descriptor.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...)) 893 _ = remote.Delete(ref, regClientOpts...) 894 } 895 return ref, remoteImage, cleanup 896 } 897 898 func mkimageindex(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) { 899 ref, err := name.ParseReference(n, name.WeakValidation) 900 if err != nil { 901 t.Fatal(err) 902 } 903 ii, err := random.Index(512, 5, 4) 904 if err != nil { 905 t.Fatal(err) 906 } 907 908 regClientOpts := registryClientOpts(context.Background()) 909 910 if err := remote.WriteIndex(ref, ii, regClientOpts...); err != nil { 911 t.Fatal(err) 912 } 913 914 remoteIndex, err := remote.Get(ref, regClientOpts...) 915 if err != nil { 916 t.Fatal(err) 917 } 918 919 cleanup := func() { 920 _ = remote.Delete(ref, regClientOpts...) 921 ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteIndex.Descriptor.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...)) 922 _ = remote.Delete(ref, regClientOpts...) 923 } 924 return ref, remoteIndex, cleanup 925 } 926 927 func must(err error, t *testing.T) { 928 t.Helper() 929 if err != nil { 930 t.Fatal(err) 931 } 932 } 933 934 func mustErr(err error, t *testing.T) { 935 t.Helper() 936 if err == nil { 937 t.Fatal("expected error") 938 } 939 } 940 941 func equals(v1, v2 interface{}, t *testing.T) { 942 if diff := cmp.Diff(v1, v2); diff != "" { 943 t.Error(diff) 944 } 945 } 946 947 func reg(t *testing.T) (string, func()) { 948 repo := os.Getenv("COSIGN_TEST_REPO") 949 if repo != "" { 950 return repo, func() {} 951 } 952 953 t.Log("COSIGN_TEST_REPO unset, using fake registry") 954 r := httptest.NewServer(registry.New()) 955 u, err := url.Parse(r.URL) 956 if err != nil { 957 t.Fatal(err) 958 } 959 return u.Host, r.Close 960 } 961 962 func registryClientOpts(ctx context.Context) []remote.Option { 963 return []remote.Option{ 964 remote.WithAuthFromKeychain(authn.DefaultKeychain), 965 remote.WithContext(ctx), 966 } 967 }