github.com/sigstore/cosign@v1.13.6/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 37 "github.com/google/go-cmp/cmp" 38 "github.com/google/go-containerregistry/pkg/authn" 39 "github.com/google/go-containerregistry/pkg/name" 40 "github.com/google/go-containerregistry/pkg/registry" 41 "github.com/google/go-containerregistry/pkg/v1/random" 42 "github.com/google/go-containerregistry/pkg/v1/remote" 43 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 44 k8s "k8s.io/client-go/kubernetes" 45 "k8s.io/client-go/tools/clientcmd" 46 47 // Initialize all known client auth plugins 48 _ "k8s.io/client-go/plugin/pkg/client/auth" 49 50 "github.com/sigstore/cosign/cmd/cosign/cli" 51 "github.com/sigstore/cosign/cmd/cosign/cli/attach" 52 "github.com/sigstore/cosign/cmd/cosign/cli/attest" 53 "github.com/sigstore/cosign/cmd/cosign/cli/download" 54 "github.com/sigstore/cosign/cmd/cosign/cli/generate" 55 "github.com/sigstore/cosign/cmd/cosign/cli/options" 56 "github.com/sigstore/cosign/cmd/cosign/cli/publickey" 57 "github.com/sigstore/cosign/cmd/cosign/cli/sign" 58 "github.com/sigstore/cosign/cmd/cosign/cli/upload" 59 cliverify "github.com/sigstore/cosign/cmd/cosign/cli/verify" 60 "github.com/sigstore/cosign/pkg/cosign" 61 "github.com/sigstore/cosign/pkg/cosign/env" 62 "github.com/sigstore/cosign/pkg/cosign/kubernetes" 63 cremote "github.com/sigstore/cosign/pkg/cosign/remote" 64 "github.com/sigstore/cosign/pkg/oci/mutate" 65 ociremote "github.com/sigstore/cosign/pkg/oci/remote" 66 "github.com/sigstore/cosign/pkg/sget" 67 sigs "github.com/sigstore/cosign/pkg/signature" 68 "github.com/sigstore/sigstore/pkg/signature/payload" 69 ) 70 71 const ( 72 serverEnv = "REKOR_SERVER" 73 rekorURL = "https://rekor.sigstore.dev" 74 ) 75 76 var keyPass = []byte("hello") 77 78 var passFunc = func(_ bool) ([]byte, error) { 79 return keyPass, nil 80 } 81 82 var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string) error { 83 cmd := cliverify.VerifyCommand{ 84 KeyRef: keyRef, 85 RekorURL: rekorURL, 86 CheckClaims: checkClaims, 87 Annotations: sigs.AnnotationsMap{Annotations: annotations}, 88 Attachment: attachment, 89 HashAlgorithm: crypto.SHA256, 90 } 91 92 args := []string{imageRef} 93 94 return cmd.Exec(context.Background(), args) 95 } 96 97 // Used to verify local images stored on disk 98 var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error { 99 cmd := cliverify.VerifyCommand{ 100 KeyRef: keyRef, 101 CheckClaims: checkClaims, 102 Annotations: sigs.AnnotationsMap{Annotations: annotations}, 103 Attachment: attachment, 104 HashAlgorithm: crypto.SHA256, 105 LocalImage: true, 106 } 107 108 args := []string{path} 109 110 return cmd.Exec(context.Background(), args) 111 } 112 113 var ro = &options.RootOptions{Timeout: options.DefaultTimeout} 114 115 func TestSignVerify(t *testing.T) { 116 repo, stop := reg(t) 117 defer stop() 118 td := t.TempDir() 119 120 imgName := path.Join(repo, "cosign-e2e") 121 122 _, _, cleanup := mkimage(t, imgName) 123 defer cleanup() 124 125 _, privKeyPath, pubKeyPath := keypair(t, td) 126 127 ctx := context.Background() 128 // Verify should fail at first 129 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 130 // So should download 131 mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 132 133 // Now sign the image 134 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 135 so := options.SignOptions{ 136 Upload: true, 137 } 138 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 139 140 // Now verify and download should work! 141 must(verify(pubKeyPath, imgName, true, nil, ""), t) 142 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 143 144 // Look for a specific annotation 145 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t) 146 147 so.AnnotationOptions = options.AnnotationOptions{ 148 Annotations: []string{"foo=bar"}, 149 } 150 // Sign the image with an annotation 151 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 152 153 // It should match this time. 154 must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t) 155 156 // But two doesn't work 157 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, ""), t) 158 } 159 160 func TestSignVerifyClean(t *testing.T) { 161 repo, stop := reg(t) 162 defer stop() 163 td := t.TempDir() 164 165 imgName := path.Join(repo, "cosign-e2e") 166 167 _, _, _ = mkimage(t, imgName) 168 169 _, privKeyPath, pubKeyPath := keypair(t, td) 170 171 ctx := context.Background() 172 173 // Now sign the image 174 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 175 so := options.SignOptions{ 176 Upload: true, 177 } 178 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 179 180 // Now verify and download should work! 181 must(verify(pubKeyPath, imgName, true, nil, ""), t) 182 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 183 184 // Now clean signature from the given image 185 must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) 186 187 // It doesn't work 188 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 189 } 190 191 func TestImportSignVerifyClean(t *testing.T) { 192 193 repo, stop := reg(t) 194 defer stop() 195 td := t.TempDir() 196 197 imgName := path.Join(repo, "cosign-e2e") 198 199 _, _, _ = mkimage(t, imgName) 200 201 _, privKeyPath, pubKeyPath := importKeyPair(t, td) 202 203 ctx := context.Background() 204 205 // Now sign the image 206 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 207 so := options.SignOptions{ 208 Upload: true, 209 } 210 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 211 212 // Now verify and download should work! 213 must(verify(pubKeyPath, imgName, true, nil, ""), t) 214 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 215 216 // Now clean signature from the given image 217 must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) 218 219 // It doesn't work 220 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 221 } 222 223 func TestAttestVerify(t *testing.T) { 224 attestVerify(t, 225 "slsaprovenance", 226 `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`, 227 `predicate: builder: id: "2"`, 228 `predicate: builder: id: "1"`, 229 ) 230 } 231 232 func TestAttestVerifySPDXJSON(t *testing.T) { 233 attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.spdx.json") 234 if err != nil { 235 t.Fatal(err) 236 } 237 attestVerify(t, 238 "spdxjson", 239 string(attestationBytes), 240 `predicate: Data: spdxVersion: "SPDX-2.2"`, 241 `predicate: Data: spdxVersion: "SPDX-9.9"`, 242 ) 243 } 244 245 func TestAttestVerifyCycloneDXJSON(t *testing.T) { 246 attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.cyclonedx.json") 247 if err != nil { 248 t.Fatal(err) 249 } 250 attestVerify(t, 251 "cyclonedx", 252 string(attestationBytes), 253 `predicate: Data: specVersion: "1.4"`, 254 `predicate: Data: specVersion: "7.7"`, 255 ) 256 } 257 258 func TestAttestVerifyURI(t *testing.T) { 259 attestationBytes, err := os.ReadFile("./testdata/test-result.json") 260 if err != nil { 261 t.Fatal(err) 262 } 263 attestVerify(t, 264 "https://example.com/TestResult/v1", 265 string(attestationBytes), 266 `predicate: passed: true`, 267 `predicate: passed: false"`, 268 ) 269 } 270 271 func attestVerify(t *testing.T, predicateType, attestation, goodCue, badCue string) { 272 repo, stop := reg(t) 273 defer stop() 274 td := t.TempDir() 275 276 var imgName, attestationPath string 277 if _, err := url.ParseRequestURI(predicateType); err == nil { 278 // If the predicate type is URI, it cannot be included as image name and path. 279 imgName = path.Join(repo, "cosign-attest-uri-e2e-image") 280 attestationPath = filepath.Join(td, "cosign-attest-uri-e2e-attestation") 281 } else { 282 imgName = path.Join(repo, fmt.Sprintf("cosign-attest-%s-e2e-image", predicateType)) 283 attestationPath = filepath.Join(td, fmt.Sprintf("cosign-attest-%s-e2e-attestation", predicateType)) 284 } 285 286 _, _, cleanup := mkimage(t, imgName) 287 defer cleanup() 288 289 _, privKeyPath, pubKeyPath := keypair(t, td) 290 291 ctx := context.Background() 292 293 // Verify should fail at first 294 verifyAttestation := cliverify.VerifyAttestationCommand{ 295 KeyRef: pubKeyPath, 296 } 297 298 // Fail case when using without type and policy flag 299 mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) 300 301 if err := os.WriteFile(attestationPath, []byte(attestation), 0600); err != nil { 302 t.Fatal(err) 303 } 304 305 // Now attest the image 306 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 307 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, attestationPath, false, 308 predicateType, false, 30*time.Second, false), t) 309 310 // Use cue to verify attestation 311 policyPath := filepath.Join(td, "policy.cue") 312 verifyAttestation.PredicateType = predicateType 313 verifyAttestation.Policies = []string{policyPath} 314 315 // Fail case 316 if err := os.WriteFile(policyPath, []byte(badCue), 0600); err != nil { 317 t.Fatal(err) 318 } 319 mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) 320 321 // Success case 322 if err := os.WriteFile(policyPath, []byte(goodCue), 0600); err != nil { 323 t.Fatal(err) 324 } 325 must(verifyAttestation.Exec(ctx, []string{imgName}), t) 326 327 // Look for a specific annotation 328 mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, ""), t) 329 } 330 331 func TestAttestationReplaceCreate(t *testing.T) { 332 repo, stop := reg(t) 333 defer stop() 334 td := t.TempDir() 335 336 imgName := path.Join(repo, "cosign-attest-replace-e2e") 337 338 _, _, cleanup := mkimage(t, imgName) 339 defer cleanup() 340 341 _, privKeyPath, _ := keypair(t, td) 342 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 343 344 ctx := context.Background() 345 346 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` 347 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") 348 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { 349 t.Fatal(err) 350 } 351 352 ref, err := name.ParseReference(imgName) 353 if err != nil { 354 t.Fatal(err) 355 } 356 regOpts := options.RegistryOptions{} 357 ociremoteOpts, err := regOpts.ClientOpts(ctx) 358 if err != nil { 359 t.Fatal(err) 360 } 361 362 // Attest with replace=true to create an attestation 363 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, slsaAttestationPath, false, 364 "slsaprovenance", true, 30*time.Second, false), t) 365 366 // Download and count the attestations 367 attestations, err := cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...) 368 if err != nil { 369 t.Fatal(err) 370 } 371 if len(attestations) != 1 { 372 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) 373 } 374 } 375 376 func TestExcessiveAttestations(t *testing.T) { 377 // skipping tst it is falky and taking too long 378 t.Skip() 379 repo, stop := reg(t) 380 defer stop() 381 td := t.TempDir() 382 383 imgName := path.Join(repo, "cosign-attest-download-e2e") 384 385 _, _, cleanup := mkimage(t, imgName) 386 defer cleanup() 387 388 _, privKeyPath, _ := keypair(t, td) 389 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 390 391 ctx := context.Background() 392 393 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` 394 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") 395 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { 396 t.Fatal(err) 397 } 398 399 vulnAttestation := ` 400 { 401 "invocation": { 402 "parameters": null, 403 "uri": "invocation.example.com/cosign-testing", 404 "event_id": "", 405 "builder.id": "" 406 }, 407 "scanner": { 408 "uri": "fakescanner.example.com/cosign-testing", 409 "version": "", 410 "db": { 411 "uri": "", 412 "version": "" 413 }, 414 "result": null 415 }, 416 "metadata": { 417 "scanStartedOn": "2022-04-12T00:00:00Z", 418 "scanFinishedOn": "2022-04-12T00:10:00Z" 419 } 420 } 421 ` 422 ref, err := name.ParseReference(imgName) 423 if err != nil { 424 t.Fatal(err) 425 } 426 regOpts := options.RegistryOptions{} 427 ociremoteOpts, err := regOpts.ClientOpts(ctx) 428 if err != nil { 429 t.Fatal(err) 430 } 431 432 for i := 0; i < 102; i++ { 433 vulnAttestationPath := filepath.Join(td, fmt.Sprintf("attestation-%d.vuln.json", i)) 434 if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil { 435 t.Fatal(err) 436 } 437 438 // Attest to create a vuln attestation 439 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, vulnAttestationPath, false, 440 "vuln", false, 30*time.Second, false), t) 441 } 442 443 _, err = cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...) 444 if err == nil { 445 t.Fatalf("Expected an error, but 'err' was 'nil'") 446 } 447 expectedError := "maximum number of attestations on an image is 100, found 102" 448 if err.Error() != expectedError { 449 t.Errorf("Exted the error to be: '%s' but it was '%s'", expectedError, err.Error()) 450 } 451 } 452 453 func TestAttestationReplace(t *testing.T) { 454 repo, stop := reg(t) 455 defer stop() 456 td := t.TempDir() 457 458 imgName := path.Join(repo, "cosign-attest-replace-e2e") 459 460 _, _, cleanup := mkimage(t, imgName) 461 defer cleanup() 462 463 _, privKeyPath, _ := keypair(t, td) 464 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 465 466 ctx := context.Background() 467 468 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` 469 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") 470 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { 471 t.Fatal(err) 472 } 473 474 ref, err := name.ParseReference(imgName) 475 if err != nil { 476 t.Fatal(err) 477 } 478 regOpts := options.RegistryOptions{} 479 ociremoteOpts, err := regOpts.ClientOpts(ctx) 480 if err != nil { 481 t.Fatal(err) 482 } 483 484 // Attest once with replace=false creating an attestation 485 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, slsaAttestationPath, false, 486 "slsaprovenance", false, 30*time.Second, false), t) 487 488 // Download and count the attestations 489 attestations, err := cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...) 490 if err != nil { 491 t.Fatal(err) 492 } 493 if len(attestations) != 1 { 494 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) 495 } 496 497 // Attest again with replace=true, replacing the previous attestation 498 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, slsaAttestationPath, false, 499 "slsaprovenance", true, 30*time.Second, false), t) 500 attestations, err = cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...) 501 502 // Download and count the attestations 503 if err != nil { 504 t.Fatal(err) 505 } 506 if len(attestations) != 1 { 507 t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) 508 } 509 510 // Attest once more replace=true using a different predicate, to ensure it adds a new attestation 511 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, slsaAttestationPath, false, 512 "custom", true, 30*time.Second, false), t) 513 514 // Download and count the attestations 515 attestations, err = cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...) 516 if err != nil { 517 t.Fatal(err) 518 } 519 if len(attestations) != 2 { 520 t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations))) 521 } 522 } 523 524 func TestRekorBundle(t *testing.T) { 525 // turn on the tlog 526 defer setenv(t, env.VariableExperimental.String(), "1")() 527 528 repo, stop := reg(t) 529 defer stop() 530 td := t.TempDir() 531 532 imgName := path.Join(repo, "cosign-e2e") 533 534 _, _, cleanup := mkimage(t, imgName) 535 defer cleanup() 536 537 _, privKeyPath, pubKeyPath := keypair(t, td) 538 539 ko := options.KeyOpts{ 540 KeyRef: privKeyPath, 541 PassFunc: passFunc, 542 RekorURL: rekorURL, 543 } 544 so := options.SignOptions{ 545 Upload: true, 546 } 547 548 // Sign the image 549 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 550 // Make sure verify works 551 must(verify(pubKeyPath, imgName, true, nil, ""), t) 552 553 // Make sure offline verification works with bundling 554 // use rekor prod since we have hardcoded the public key 555 os.Setenv(serverEnv, "notreal") 556 must(verify(pubKeyPath, imgName, true, nil, ""), t) 557 } 558 559 func TestDuplicateSign(t *testing.T) { 560 repo, stop := reg(t) 561 defer stop() 562 td := t.TempDir() 563 564 imgName := path.Join(repo, "cosign-e2e") 565 566 ref, _, cleanup := mkimage(t, imgName) 567 defer cleanup() 568 569 _, privKeyPath, pubKeyPath := keypair(t, td) 570 571 ctx := context.Background() 572 // Verify should fail at first 573 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 574 // So should download 575 mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 576 577 // Now sign the image 578 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 579 so := options.SignOptions{ 580 Upload: true, 581 } 582 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 583 584 // Now verify and download should work! 585 must(verify(pubKeyPath, imgName, true, nil, ""), t) 586 must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) 587 588 // Signing again should work just fine... 589 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 590 591 se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...)) 592 must(err, t) 593 sigs, err := se.Signatures() 594 must(err, t) 595 signatures, err := sigs.Get() 596 must(err, t) 597 598 if len(signatures) > 1 { 599 t.Errorf("expected there to only be one signature, got %v", signatures) 600 } 601 } 602 603 func TestExcessiveSignatures(t *testing.T) { 604 repo, stop := reg(t) 605 defer stop() 606 td := t.TempDir() 607 608 imgName := path.Join(repo, "cosign-e2e") 609 610 _, _, cleanup := mkimage(t, imgName) 611 defer cleanup() 612 613 ctx := context.Background() 614 615 for i := 0; i < 102; i++ { 616 _, privKeyPath, _ := keypair(t, td) 617 618 // Sign the image 619 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 620 so := options.SignOptions{ 621 Upload: true, 622 } 623 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 624 } 625 err := download.SignatureCmd(ctx, options.RegistryOptions{}, imgName) 626 if err == nil { 627 t.Fatal("Expected an error, but 'err' was 'nil'") 628 } 629 expectedErr := "maximum number of signatures on an image is 100, found 102" 630 if err.Error() != expectedErr { 631 t.Fatalf("Expected the error '%s', but got the error '%s'", expectedErr, err.Error()) 632 } 633 } 634 635 func TestKeyURLVerify(t *testing.T) { 636 // TODO: re-enable once distroless images are being signed by the new client 637 t.Skip() 638 // Verify that an image can be verified via key url 639 keyRef := "https://raw.githubusercontent.com/GoogleContainerTools/distroless/main/cosign.pub" 640 img := "gcr.io/distroless/base:latest" 641 642 must(verify(keyRef, img, true, nil, ""), t) 643 } 644 645 func TestGenerateKeyPairEnvVar(t *testing.T) { 646 defer setenv(t, "COSIGN_PASSWORD", "foo")() 647 keys, err := cosign.GenerateKeyPair(generate.GetPass) 648 if err != nil { 649 t.Fatal(err) 650 } 651 if _, err := cosign.LoadPrivateKey(keys.PrivateBytes, []byte("foo")); err != nil { 652 t.Fatal(err) 653 } 654 } 655 656 func TestGenerateKeyPairK8s(t *testing.T) { 657 td := t.TempDir() 658 wd, err := os.Getwd() 659 if err != nil { 660 t.Fatal(err) 661 } 662 if err := os.Chdir(td); err != nil { 663 t.Fatal(err) 664 } 665 defer func() { 666 os.Chdir(wd) 667 }() 668 password := "foo" 669 defer setenv(t, "COSIGN_PASSWORD", password)() 670 ctx := context.Background() 671 name := "cosign-secret" 672 namespace := "default" 673 if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("k8s://%s/%s", namespace, name), generate.GetPass); err != nil { 674 t.Fatal(err) 675 } 676 // make sure the secret actually exists 677 678 cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 679 clientcmd.NewDefaultClientConfigLoadingRules(), nil).ClientConfig() 680 if err != nil { 681 t.Fatal(err) 682 } 683 client, err := k8s.NewForConfig(cfg) 684 if err != nil { 685 t.Fatal(err) 686 } 687 s, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) 688 if err != nil { 689 t.Fatal(err) 690 } 691 if v, ok := s.Data["cosign.password"]; !ok || string(v) != password { 692 t.Fatalf("password is incorrect, got %v expected %v", v, "foo") 693 } 694 } 695 696 func TestMultipleSignatures(t *testing.T) { 697 repo, stop := reg(t) 698 defer stop() 699 700 td1 := t.TempDir() 701 td2 := t.TempDir() 702 703 imgName := path.Join(repo, "cosign-e2e") 704 705 _, _, cleanup := mkimage(t, imgName) 706 defer cleanup() 707 708 _, priv1, pub1 := keypair(t, td1) 709 _, priv2, pub2 := keypair(t, td2) 710 711 // Verify should fail at first for both keys 712 mustErr(verify(pub1, imgName, true, nil, ""), t) 713 mustErr(verify(pub2, imgName, true, nil, ""), t) 714 715 // Now sign the image with one key 716 ko := options.KeyOpts{KeyRef: priv1, PassFunc: passFunc} 717 so := options.SignOptions{ 718 Upload: true, 719 } 720 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 721 // Now verify should work with that one, but not the other 722 must(verify(pub1, imgName, true, nil, ""), t) 723 mustErr(verify(pub2, imgName, true, nil, ""), t) 724 725 // Now sign with the other key too 726 ko.KeyRef = priv2 727 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 728 729 // Now verify should work with both 730 must(verify(pub1, imgName, true, nil, ""), t) 731 must(verify(pub2, imgName, true, nil, ""), t) 732 } 733 734 func TestSignBlob(t *testing.T) { 735 blob := "someblob" 736 td1 := t.TempDir() 737 td2 := t.TempDir() 738 t.Cleanup(func() { 739 os.RemoveAll(td1) 740 os.RemoveAll(td2) 741 }) 742 bp := filepath.Join(td1, blob) 743 744 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { 745 t.Fatal(err) 746 } 747 748 _, privKeyPath1, pubKeyPath1 := keypair(t, td1) 749 _, _, pubKeyPath2 := keypair(t, td2) 750 751 ctx := context.Background() 752 753 ko1 := options.KeyOpts{ 754 KeyRef: pubKeyPath1, 755 } 756 ko2 := options.KeyOpts{ 757 KeyRef: pubKeyPath2, 758 } 759 // Verify should fail on a bad input 760 mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certIdentity*/, "" /*certOidcIssuer*/, "" /*certChain*/, "badsig", blob, "" /*certGithubWorkflowTrigger*/, "" /*certGithubWorkflowName*/, "" /*certGithubWorkflowSha*/, "" /*certGithubWorkflowRepository*/, "" /*certGithubWorkflowRef*/, false), t) 761 mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certIdentity*/, "" /*certOidcIssuer*/, "" /*certChain*/, "badsig", blob, "" /*certGithubWorkflowTrigger*/, "" /*certGithubWorkflowName*/, "" /*certGithubWorkflowSha*/, "" /*certGithubWorkflowRepository*/, "" /*certGithubWorkflowRef*/, false), t) 762 763 // Now sign the blob with one key 764 ko := options.KeyOpts{ 765 KeyRef: privKeyPath1, 766 PassFunc: passFunc, 767 } 768 sig, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", "") 769 if err != nil { 770 t.Fatal(err) 771 } 772 // Now verify should work with that one, but not the other 773 must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certIdentity*/, "" /*certOidcIssuer*/, "" /*certChain*/, string(sig), bp, "" /*certGithubWorkflowTrigger*/, "" /*certGithubWorkflowSha*/, "" /*certGithubWorkflowName*/, "" /*certGithubWorkflowRepository*/, "" /*certGithubWorkflowRef*/, false), t) 774 mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certIdentity*/, "" /*certOidcIssuer*/, "" /*certChain*/, string(sig), bp, "" /*certGithubWorkflowTrigger*/, "" /*certGithubWorkflowSha*/, "" /*certGithubWorkflowName*/, "" /*certGithubWorkflowRepository*/, "" /*certGithubWorkflowRef*/, false), t) 775 } 776 777 func TestSignBlobBundle(t *testing.T) { 778 blob := "someblob" 779 td1 := t.TempDir() 780 t.Cleanup(func() { 781 os.RemoveAll(td1) 782 }) 783 bp := filepath.Join(td1, blob) 784 bundlePath := filepath.Join(td1, "bundle.sig") 785 786 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { 787 t.Fatal(err) 788 } 789 790 _, privKeyPath1, pubKeyPath1 := keypair(t, td1) 791 792 ctx := context.Background() 793 794 ko1 := options.KeyOpts{ 795 KeyRef: pubKeyPath1, 796 BundlePath: bundlePath, 797 } 798 // Verify should fail on a bad input 799 mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certIdentity*/, "" /*certOidcIssuer*/, "" /*certChain*/, "" /*sigRef*/, blob, "" /*certGithubWorkflowTrigger*/, "" /*certGithubWorkflowSha*/, "" /*certGithubWorkflowName*/, "" /*certGithubWorkflowRepository*/, "" /*certGithubWorkflowRef*/, false), t) 800 801 // Now sign the blob with one key 802 ko := options.KeyOpts{ 803 KeyRef: privKeyPath1, 804 PassFunc: passFunc, 805 BundlePath: bundlePath, 806 RekorURL: rekorURL, 807 } 808 if _, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", ""); err != nil { 809 t.Fatal(err) 810 } 811 // Now verify should work 812 must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certIdentity*/, "" /*certOidcIssuer*/, "" /*certChain*/, "" /*sigRef*/, bp, "" /*certGithubWorkflowTrigger*/, "" /*certGithubWorkflowSha*/, "" /*certGithubWorkflowName*/, "" /*certGithubWorkflowRepository*/, "" /*certGithubWorkflowRef*/, false), t) 813 814 // Now we turn on the tlog and sign again 815 defer setenv(t, env.VariableExperimental.String(), "1")() 816 if _, err := sign.SignBlobCmd(ro, ko, options.RegistryOptions{}, bp, true, "", ""); err != nil { 817 t.Fatal(err) 818 } 819 820 // Point to a fake rekor server to make sure offline verification of the tlog entry works 821 os.Setenv(serverEnv, "notreal") 822 must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certIdentity*/, "" /*certOidcIssuer*/, "" /*certChain*/, "" /*sigRef*/, bp, "" /*certGithubWorkflowTrigger*/, "" /*certGithubWorkflowSha*/, "" /*certGithubWorkflowName*/, "" /*certGithubWorkflowRepository*/, "" /*certGithubWorkflowRef*/, false), t) 823 } 824 825 func TestGenerate(t *testing.T) { 826 repo, stop := reg(t) 827 defer stop() 828 829 imgName := path.Join(repo, "cosign-e2e") 830 _, desc, cleanup := mkimage(t, imgName) 831 defer cleanup() 832 833 // Generate the payload for the image, and check the digest. 834 b := bytes.Buffer{} 835 must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) 836 ss := payload.SimpleContainerImage{} 837 must(json.Unmarshal(b.Bytes(), &ss), t) 838 839 equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) 840 841 // Now try with some annotations. 842 b.Reset() 843 a := map[string]interface{}{"foo": "bar"} 844 must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, a, &b), t) 845 must(json.Unmarshal(b.Bytes(), &ss), t) 846 847 equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) 848 equals(ss.Optional["foo"], "bar", t) 849 } 850 851 func keypair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { 852 wd, err := os.Getwd() 853 if err != nil { 854 t.Fatal(err) 855 } 856 if err := os.Chdir(td); err != nil { 857 t.Fatal(err) 858 } 859 defer func() { 860 os.Chdir(wd) 861 }() 862 keys, err := cosign.GenerateKeyPair(passFunc) 863 if err != nil { 864 t.Fatal(err) 865 } 866 867 privKeyPath := filepath.Join(td, "cosign.key") 868 if err := os.WriteFile(privKeyPath, keys.PrivateBytes, 0600); err != nil { 869 t.Fatal(err) 870 } 871 872 pubKeyPath := filepath.Join(td, "cosign.pub") 873 if err := os.WriteFile(pubKeyPath, keys.PublicBytes, 0600); err != nil { 874 t.Fatal(err) 875 } 876 return keys, privKeyPath, pubKeyPath 877 } 878 879 func importKeyPair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { 880 881 const validrsa1 = `-----BEGIN RSA PRIVATE KEY----- 882 MIIEogIBAAKCAQEAx5piWVlE62NnZ0UzJ8Z6oKiKOC4dbOZ1HsNhIRtqkM+Oq4G+ 883 25yq6P+0JU/Qvr9veOGEb3R/J9u8JBo+hv2i5X8OtgvP2V2pi6f1s6vK7L0+6uRb 884 4YTT/UdMshaVf97MgEqbq41Jf/cuvh+3AV0tZ1BpixZg4aXMKpY6HUP69lbsu27o 885 SUN1myMv7TSgZiV4CYs3l/gkEfpysBptWlcHRuw5RsB+C0RbjRtbJ/5VxmE/vd3M 886 lafd5t1WSpMb8yf0a84u5NFaXwZ7CweMfXeOddS0yb19ShSuW3PPRadruBM1mq15 887 js9GfagPxDS75Imcs+fA62lWvHxEujTGjYHxawIDAQABAoIBAH+sgLwmHa9zJfEo 888 klAe5NFe/QpydN/ziXbkAnzqzH9URC3wD+TpkWj4JoK3Sw635NWtasjf+3XDV9S/ 889 9L7j/g5N91r6sziWcJykEsWaXXKQmm4lI6BdFjwsHyLKz1W7bZOiJXDWLu1rbrqu 890 DqEQuLoc9WXCKrYrFy0maoXNtfla/1p05kKN0bMigcnnyAQ+xBTwoyco4tkIz5se 891 IYxorz7qzXrkHQI+knz5BawmNe3ekoSaXUPoLoOR7TRTGsLteL5yukvWAi8S/0rE 892 gftC+PZCQpoQhSUYq7wXe7RowJ1f+kXb7HsSedOTfTSW1D/pUb/uW+CcRKig42ZI 893 I9H9TAECgYEA5XGBML6fJyWVqx64sHbUAjQsmQ0RwU6Zo7sqHIEPf6tYVYp7KtzK 894 KOfi8seOOL5FSy4pjCo11Dzyrh9bn45RNmtjSYTgOnVPSoCfuRNfOcpG+/wCHjYf 895 EjDvdrCpbg59kVUeaMeBDiyWAlM48HJAn8O7ez2U/iKQCyJmOIwFhSkCgYEA3rSz 896 Fi1NzqYWxWos4NBmg8iKcQ9SMkmPdgRLAs/WNnZJ8fdgJZwihevkXGytRGJEmav2 897 GMKRx1g6ey8fjXTQH9WM8X/kJC5fv8wLHnUCH/K3Mcp9CYwn7PFvSnBr4kQoc/el 898 bURhcF1+/opEC8vNX/Wk3zAG7Xs1PREXlH2SIHMCgYBV/3kgwBH/JkM25EjtO1yz 899 hsLAivmAruk/SUO7c1RP0fVF+qW3pxHOyztxLALOmeJ3D1JbSubqKf377Zz17O3b 900 q9yHDdrNjnKtxhAX2n7ytjJs+EQC9t4mf1kB761RpvTBqFnBhCWHHocLUA4jcW9v 901 cnmu86IIrwO2aKpPv4vCIQKBgHU9gY3qOazRSOmSlJ+hdmZn+2G7pBTvHsQNTIPl 902 cCrpqNHl3crO4GnKHkT9vVVjuiOAIKU2QNJFwzu4Og8Y8LvhizpTjoHxm9x3iV72 903 UDELcJ+YrqyJCTe2flUcy96o7Pbn50GXnwgtYD6WAW6IUszyn2ITgYIhu4wzZEt6 904 s6O7AoGAPTKbRA87L34LMlXyUBJma+etMARIP1zu8bXJ7hSJeMcog8zaLczN7ruT 905 pGAaLxggvtvuncMuTrG+cdmsR9SafSFKRS92NCxhOUonQ+NP6mLskIGzJZoQ5JvQ 906 qGzRVIDGbNkrVHM0IsAtHRpC0rYrtZY+9OwiraGcsqUMLwwQdCA= 907 -----END RSA PRIVATE KEY-----` 908 909 wd, err := os.Getwd() 910 if err != nil { 911 t.Fatal(err) 912 } 913 if err := os.Chdir(td); err != nil { 914 t.Fatal(err) 915 } 916 defer func() { 917 os.Chdir(wd) 918 }() 919 920 err = os.WriteFile("validrsa1.key", []byte(validrsa1), 0600) 921 if err != nil { 922 t.Fatal(err) 923 } 924 925 keys, err := cosign.ImportKeyPair("validrsa1.key", passFunc) 926 if err != nil { 927 t.Fatal(err) 928 } 929 930 privKeyPath := filepath.Join(td, "import-cosign.key") 931 if err := os.WriteFile(privKeyPath, keys.PrivateBytes, 0600); err != nil { 932 t.Fatal(err) 933 } 934 935 pubKeyPath := filepath.Join(td, "import-cosign.pub") 936 if err := os.WriteFile(pubKeyPath, keys.PublicBytes, 0600); err != nil { 937 t.Fatal(err) 938 } 939 return keys, privKeyPath, pubKeyPath 940 941 } 942 943 func TestUploadDownload(t *testing.T) { 944 repo, stop := reg(t) 945 defer stop() 946 td := t.TempDir() 947 ctx := context.Background() 948 949 testCases := map[string]struct { 950 signature string 951 signatureType attach.SignatureArgType 952 expectedErr bool 953 }{ 954 "file containing signature": { 955 signature: "testsignaturefile", 956 signatureType: attach.FileSignature, 957 expectedErr: false, 958 }, 959 "raw signature as argument": { 960 signature: "testsignatureraw", 961 signatureType: attach.RawSignature, 962 expectedErr: false, 963 }, 964 "empty signature as argument": { 965 signature: "", 966 signatureType: attach.RawSignature, 967 expectedErr: true, 968 }, 969 } 970 971 imgName := path.Join(repo, "cosign-e2e") 972 for testName, testCase := range testCases { 973 t.Run(testName, func(t *testing.T) { 974 ref, _, cleanup := mkimage(t, imgName) 975 payload := "testpayload" 976 payloadPath := mkfile(payload, td, t) 977 signature := base64.StdEncoding.EncodeToString([]byte(testCase.signature)) 978 979 var sigRef string 980 if testCase.signatureType == attach.FileSignature { 981 sigRef = mkfile(signature, td, t) 982 } else { 983 sigRef = signature 984 } 985 986 // Upload it! 987 err := attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadPath, imgName) 988 if testCase.expectedErr { 989 mustErr(err, t) 990 } else { 991 must(err, t) 992 } 993 994 // Now download it! 995 se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...)) 996 must(err, t) 997 sigs, err := se.Signatures() 998 must(err, t) 999 signatures, err := sigs.Get() 1000 must(err, t) 1001 1002 if testCase.expectedErr { 1003 if len(signatures) != 0 { 1004 t.Fatalf("unexpected signatures %d, wanted 0", len(signatures)) 1005 } 1006 } else { 1007 if len(signatures) != 1 { 1008 t.Fatalf("unexpected signatures %d, wanted 1", len(signatures)) 1009 } 1010 1011 if b64sig, err := signatures[0].Base64Signature(); err != nil { 1012 t.Fatalf("Base64Signature() = %v", err) 1013 } else if diff := cmp.Diff(b64sig, signature); diff != "" { 1014 t.Error(diff) 1015 } 1016 1017 if p, err := signatures[0].Payload(); err != nil { 1018 t.Fatalf("Payload() = %v", err) 1019 } else if diff := cmp.Diff(p, []byte(payload)); diff != "" { 1020 t.Error(diff) 1021 } 1022 } 1023 1024 // Now delete it! 1025 cleanup() 1026 }) 1027 } 1028 } 1029 1030 func TestUploadBlob(t *testing.T) { 1031 repo, stop := reg(t) 1032 defer stop() 1033 td := t.TempDir() 1034 ctx := context.Background() 1035 1036 imgName := path.Join(repo, "/cosign-upload-e2e") 1037 payload := "testpayload" 1038 payloadPath := mkfile(payload, td, t) 1039 1040 // Upload it! 1041 files := []cremote.File{cremote.FileFromFlag(payloadPath)} 1042 must(upload.BlobCmd(ctx, options.RegistryOptions{}, files, nil, "", imgName), t) 1043 1044 // Check it 1045 ref, err := name.ParseReference(imgName) 1046 if err != nil { 1047 t.Fatal(err) 1048 } 1049 1050 // Now download it with sget (this should fail by tag) 1051 if err := sget.New(imgName, "", os.Stdout).Do(ctx); err == nil { 1052 t.Error("expected download to fail") 1053 } 1054 1055 img, err := remote.Image(ref) 1056 if err != nil { 1057 t.Fatal(err) 1058 } 1059 dgst, err := img.Digest() 1060 if err != nil { 1061 t.Fatal(err) 1062 } 1063 1064 result := &bytes.Buffer{} 1065 1066 // But pass by digest 1067 if err := sget.New(imgName+"@"+dgst.String(), "", result).Do(ctx); err != nil { 1068 t.Fatal(err) 1069 } 1070 b, err := io.ReadAll(result) 1071 if err != nil { 1072 t.Fatal(err) 1073 } 1074 if string(b) != payload { 1075 t.Errorf("expected contents to be %s, got %s", payload, string(b)) 1076 } 1077 } 1078 1079 func TestSaveLoad(t *testing.T) { 1080 tests := []struct { 1081 description string 1082 getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) 1083 }{ 1084 { 1085 description: "save and load an image", 1086 getSignedEntity: mkimage, 1087 }, 1088 { 1089 description: "save and load an image index", 1090 getSignedEntity: mkimageindex, 1091 }, 1092 } 1093 for i, test := range tests { 1094 t.Run(test.description, func(t *testing.T) { 1095 repo, stop := reg(t) 1096 defer stop() 1097 keysDir := t.TempDir() 1098 1099 imgName := path.Join(repo, fmt.Sprintf("save-load-%d", i)) 1100 1101 _, _, cleanup := test.getSignedEntity(t, imgName) 1102 defer cleanup() 1103 1104 _, privKeyPath, pubKeyPath := keypair(t, keysDir) 1105 1106 ctx := context.Background() 1107 // Now sign the image and verify it 1108 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 1109 so := options.SignOptions{ 1110 Upload: true, 1111 } 1112 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 1113 must(verify(pubKeyPath, imgName, true, nil, ""), t) 1114 1115 // save the image to a temp dir 1116 imageDir := t.TempDir() 1117 must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) 1118 1119 // verify the local image using a local key 1120 must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t) 1121 1122 // load the image from the temp dir into a new image and verify the new image 1123 imgName2 := path.Join(repo, fmt.Sprintf("save-load-%d-2", i)) 1124 must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) 1125 must(verify(pubKeyPath, imgName2, true, nil, ""), t) 1126 }) 1127 } 1128 } 1129 1130 func TestSaveLoadAttestation(t *testing.T) { 1131 repo, stop := reg(t) 1132 defer stop() 1133 td := t.TempDir() 1134 1135 imgName := path.Join(repo, "save-load") 1136 1137 _, _, cleanup := mkimage(t, imgName) 1138 defer cleanup() 1139 1140 _, privKeyPath, pubKeyPath := keypair(t, td) 1141 1142 ctx := context.Background() 1143 // Now sign the image and verify it 1144 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 1145 so := options.SignOptions{ 1146 Upload: true, 1147 } 1148 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 1149 must(verify(pubKeyPath, imgName, true, nil, ""), t) 1150 1151 // now, append an attestation to the image 1152 slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` 1153 slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") 1154 if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { 1155 t.Fatal(err) 1156 } 1157 1158 // Now attest the image 1159 ko = options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} 1160 must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, slsaAttestationPath, false, 1161 "slsaprovenance", false, 30*time.Second, false), t) 1162 1163 // save the image to a temp dir 1164 imageDir := t.TempDir() 1165 must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) 1166 1167 // load the image from the temp dir into a new image and verify the new image 1168 imgName2 := path.Join(repo, "save-load-2") 1169 must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) 1170 must(verify(pubKeyPath, imgName2, true, nil, ""), t) 1171 // Use cue to verify attestation on the new image 1172 policyPath := filepath.Join(td, "policy.cue") 1173 verifyAttestation := cliverify.VerifyAttestationCommand{ 1174 KeyRef: pubKeyPath, 1175 } 1176 verifyAttestation.PredicateType = "slsaprovenance" 1177 verifyAttestation.Policies = []string{policyPath} 1178 // Success case (remote) 1179 cuePolicy := `predicate: builder: id: "2"` 1180 if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil { 1181 t.Fatal(err) 1182 } 1183 must(verifyAttestation.Exec(ctx, []string{imgName2}), t) 1184 // Success case (local) 1185 verifyAttestation.LocalImage = true 1186 must(verifyAttestation.Exec(ctx, []string{imageDir}), t) 1187 } 1188 1189 func TestAttachSBOM(t *testing.T) { 1190 repo, stop := reg(t) 1191 defer stop() 1192 ctx := context.Background() 1193 1194 imgName := path.Join(repo, "sbom-image") 1195 img, _, cleanup := mkimage(t, imgName) 1196 defer cleanup() 1197 1198 out := bytes.Buffer{} 1199 1200 _, errPl := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{Platform: "darwin/amd64"}, img.Name(), &out) 1201 if errPl == nil { 1202 t.Fatalf("Expected error when passing Platform to single arch image") 1203 } 1204 _, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, img.Name(), &out) 1205 if err == nil { 1206 t.Fatal("Expected error") 1207 } 1208 t.Log(out.String()) 1209 out.Reset() 1210 1211 // Upload it! 1212 must(attach.SBOMCmd(ctx, options.RegistryOptions{}, "./testdata/bom-go-mod.spdx", "spdx", imgName), t) 1213 1214 sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, imgName, &out) 1215 if err != nil { 1216 t.Fatal(err) 1217 } 1218 t.Log(out.String()) 1219 if len(sboms) != 1 { 1220 t.Fatalf("Expected one sbom, got %d", len(sboms)) 1221 } 1222 want, err := os.ReadFile("./testdata/bom-go-mod.spdx") 1223 if err != nil { 1224 t.Fatal(err) 1225 } 1226 if diff := cmp.Diff(string(want), sboms[0]); diff != "" { 1227 t.Errorf("diff: %s", diff) 1228 } 1229 1230 // Generate key pairs to sign the sbom 1231 td1 := t.TempDir() 1232 td2 := t.TempDir() 1233 _, privKeyPath1, pubKeyPath1 := keypair(t, td1) 1234 _, _, pubKeyPath2 := keypair(t, td2) 1235 1236 // Verify should fail on a bad input 1237 mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom"), t) 1238 mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom"), t) 1239 1240 // Now sign the sbom with one key 1241 ko1 := options.KeyOpts{KeyRef: privKeyPath1, PassFunc: passFunc} 1242 so := options.SignOptions{ 1243 Upload: true, 1244 Attachment: "sbom", 1245 } 1246 must(sign.SignCmd(ro, ko1, so, []string{imgName}), t) 1247 1248 // Now verify should work with that one, but not the other 1249 must(verify(pubKeyPath1, imgName, true, nil, "sbom"), t) 1250 mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom"), t) 1251 } 1252 1253 func setenv(t *testing.T, k, v string) func() { 1254 if err := os.Setenv(k, v); err != nil { 1255 t.Fatalf("error setting env: %v", err) 1256 } 1257 return func() { 1258 os.Unsetenv(k) 1259 } 1260 } 1261 1262 func TestTlog(t *testing.T) { 1263 repo, stop := reg(t) 1264 defer stop() 1265 td := t.TempDir() 1266 1267 imgName := path.Join(repo, "cosign-e2e") 1268 1269 _, _, cleanup := mkimage(t, imgName) 1270 defer cleanup() 1271 1272 _, privKeyPath, pubKeyPath := keypair(t, td) 1273 1274 // Verify should fail at first 1275 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 1276 1277 // Now sign the image without the tlog 1278 ko := options.KeyOpts{ 1279 KeyRef: privKeyPath, 1280 PassFunc: passFunc, 1281 RekorURL: rekorURL, 1282 } 1283 so := options.SignOptions{ 1284 Upload: true, 1285 } 1286 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 1287 1288 // Now verify should work! 1289 must(verify(pubKeyPath, imgName, true, nil, ""), t) 1290 1291 // Now we turn on the tlog! 1292 defer setenv(t, env.VariableExperimental.String(), "1")() 1293 1294 // Verify shouldn't work since we haven't put anything in it yet. 1295 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 1296 1297 // Sign again with the tlog env var on 1298 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 1299 // And now verify works! 1300 must(verify(pubKeyPath, imgName, true, nil, ""), t) 1301 } 1302 1303 func TestNoTlog(t *testing.T) { 1304 repo, stop := reg(t) 1305 defer stop() 1306 td := t.TempDir() 1307 1308 imgName := path.Join(repo, "cosign-e2e") 1309 1310 _, _, cleanup := mkimage(t, imgName) 1311 defer cleanup() 1312 1313 _, privKeyPath, pubKeyPath := keypair(t, td) 1314 1315 // Verify should fail at first 1316 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 1317 1318 // Now sign the image without the tlog 1319 ko := options.KeyOpts{ 1320 KeyRef: privKeyPath, 1321 PassFunc: passFunc, 1322 RekorURL: rekorURL, 1323 } 1324 so := options.SignOptions{ 1325 Upload: true, 1326 } 1327 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 1328 1329 // Now verify should work! 1330 must(verify(pubKeyPath, imgName, true, nil, ""), t) 1331 1332 // Now we turn on the tlog! 1333 defer setenv(t, env.VariableExperimental.String(), "1")() 1334 1335 // Verify shouldn't work since we haven't put anything in it yet. 1336 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 1337 1338 // Sign again with the tlog env var on with option to not upload tlog 1339 so = options.SignOptions{ 1340 NoTlogUpload: true, 1341 } 1342 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) 1343 // And verify it still fails. 1344 mustErr(verify(pubKeyPath, imgName, true, nil, ""), t) 1345 } 1346 1347 func TestGetPublicKeyCustomOut(t *testing.T) { 1348 td := t.TempDir() 1349 keys, privKeyPath, _ := keypair(t, td) 1350 ctx := context.Background() 1351 1352 outFile := "output.pub" 1353 outPath := filepath.Join(td, outFile) 1354 outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600) 1355 must(err, t) 1356 1357 pk := publickey.Pkopts{ 1358 KeyRef: privKeyPath, 1359 } 1360 must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t) 1361 1362 output, err := os.ReadFile(outPath) 1363 must(err, t) 1364 equals(keys.PublicBytes, output, t) 1365 } 1366 1367 func mkfile(contents, td string, t *testing.T) string { 1368 f, err := os.CreateTemp(td, "") 1369 if err != nil { 1370 t.Fatal(err) 1371 } 1372 defer f.Close() 1373 if _, err := f.Write([]byte(contents)); err != nil { 1374 t.Fatal(err) 1375 } 1376 return f.Name() 1377 } 1378 1379 func mkimage(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) { 1380 ref, err := name.ParseReference(n, name.WeakValidation) 1381 if err != nil { 1382 t.Fatal(err) 1383 } 1384 img, err := random.Image(512, 5) 1385 if err != nil { 1386 t.Fatal(err) 1387 } 1388 1389 regClientOpts := registryClientOpts(context.Background()) 1390 1391 if err := remote.Write(ref, img, regClientOpts...); err != nil { 1392 t.Fatal(err) 1393 } 1394 1395 remoteImage, err := remote.Get(ref, regClientOpts...) 1396 if err != nil { 1397 t.Fatal(err) 1398 } 1399 1400 cleanup := func() { 1401 _ = remote.Delete(ref, regClientOpts...) 1402 ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteImage.Descriptor.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...)) 1403 _ = remote.Delete(ref, regClientOpts...) 1404 } 1405 return ref, remoteImage, cleanup 1406 } 1407 1408 func mkimageindex(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) { 1409 ref, err := name.ParseReference(n, name.WeakValidation) 1410 if err != nil { 1411 t.Fatal(err) 1412 } 1413 ii, err := random.Index(512, 5, 4) 1414 if err != nil { 1415 t.Fatal(err) 1416 } 1417 1418 regClientOpts := registryClientOpts(context.Background()) 1419 1420 if err := remote.WriteIndex(ref, ii, regClientOpts...); err != nil { 1421 t.Fatal(err) 1422 } 1423 1424 remoteIndex, err := remote.Get(ref, regClientOpts...) 1425 if err != nil { 1426 t.Fatal(err) 1427 } 1428 1429 cleanup := func() { 1430 _ = remote.Delete(ref, regClientOpts...) 1431 ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteIndex.Descriptor.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...)) 1432 _ = remote.Delete(ref, regClientOpts...) 1433 } 1434 return ref, remoteIndex, cleanup 1435 } 1436 1437 func must(err error, t *testing.T) { 1438 t.Helper() 1439 if err != nil { 1440 t.Fatal(err) 1441 } 1442 } 1443 1444 func mustErr(err error, t *testing.T) { 1445 t.Helper() 1446 if err == nil { 1447 t.Fatal("expected error") 1448 } 1449 } 1450 1451 func equals(v1, v2 interface{}, t *testing.T) { 1452 if diff := cmp.Diff(v1, v2); diff != "" { 1453 t.Error(diff) 1454 } 1455 } 1456 1457 func reg(t *testing.T) (string, func()) { 1458 repo := os.Getenv("COSIGN_TEST_REPO") 1459 if repo != "" { 1460 return repo, func() {} 1461 } 1462 1463 t.Log("COSIGN_TEST_REPO unset, using fake registry") 1464 r := httptest.NewServer(registry.New()) 1465 u, err := url.Parse(r.URL) 1466 if err != nil { 1467 t.Fatal(err) 1468 } 1469 return u.Host, r.Close 1470 } 1471 1472 func registryClientOpts(ctx context.Context) []remote.Option { 1473 return []remote.Option{ 1474 remote.WithAuthFromKeychain(authn.DefaultKeychain), 1475 remote.WithContext(ctx), 1476 } 1477 } 1478 1479 // If a signature has a bundle, but *not for that signature*, cosign verification should fail 1480 // This test is pretty long, so here are the basic points: 1481 // 1. Sign image1 with a keypair, store entry in rekor 1482 // 2. Sign image2 with keypair, DO NOT store entry in rekor 1483 // 3. Take the bundle from image1 and store it on the signature in image2 1484 // 4. Verification of image2 should now fail, since the bundle is for a different signature 1485 func TestInvalidBundle(t *testing.T) { 1486 regName, stop := reg(t) 1487 defer stop() 1488 td := t.TempDir() 1489 1490 img1 := path.Join(regName, "cosign-e2e") 1491 1492 imgRef, _, cleanup := mkimage(t, img1) 1493 defer cleanup() 1494 1495 _, privKeyPath, pubKeyPath := keypair(t, td) 1496 1497 ctx := context.Background() 1498 1499 // Sign image1 and store the entry in rekor 1500 // (we're just using it for its bundle) 1501 defer setenv(t, env.VariableExperimental.String(), "1")() 1502 remoteOpts := ociremote.WithRemoteOptions(registryClientOpts(ctx)...) 1503 ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL} 1504 so := options.SignOptions{ 1505 Upload: true, 1506 Force: true, 1507 } 1508 must(sign.SignCmd(ro, ko, so, []string{img1}), t) 1509 // verify image1 1510 must(verify(pubKeyPath, img1, true, nil, ""), t) 1511 // extract the bundle from image1 1512 si, err := ociremote.SignedImage(imgRef, remoteOpts) 1513 must(err, t) 1514 imgSigs, err := si.Signatures() 1515 must(err, t) 1516 sigs, err := imgSigs.Get() 1517 must(err, t) 1518 if l := len(sigs); l != 1 { 1519 t.Error("expected one signature") 1520 } 1521 bund, err := sigs[0].Bundle() 1522 must(err, t) 1523 if bund == nil { 1524 t.Fail() 1525 } 1526 1527 // Now, we move on to image2 1528 // Sign image2 and DO NOT store the entry in rekor 1529 defer setenv(t, env.VariableExperimental.String(), "0")() 1530 img2 := path.Join(regName, "unrelated") 1531 imgRef2, _, cleanup := mkimage(t, img2) 1532 defer cleanup() 1533 so = options.SignOptions{ 1534 Upload: true, 1535 NoTlogUpload: true, 1536 } 1537 must(sign.SignCmd(ro, ko, so, []string{img2}), t) 1538 must(verify(pubKeyPath, img2, true, nil, ""), t) 1539 1540 si2, err := ociremote.SignedEntity(imgRef2, remoteOpts) 1541 must(err, t) 1542 sigs2, err := si2.Signatures() 1543 must(err, t) 1544 gottenSigs2, err := sigs2.Get() 1545 must(err, t) 1546 if len(gottenSigs2) != 1 { 1547 t.Fatal("there should be one signature") 1548 } 1549 sigsTag, err := ociremote.SignatureTag(imgRef2) 1550 if err != nil { 1551 t.Fatal(err) 1552 } 1553 1554 // At this point, we would mutate the signature to add the bundle annotation 1555 // since we don't have a function for it at the moment, mock this by deleting the signature 1556 // and pushing a new signature with the additional bundle annotation 1557 if err := remote.Delete(sigsTag); err != nil { 1558 t.Fatal(err) 1559 } 1560 mustErr(verify(pubKeyPath, img2, true, nil, ""), t) 1561 1562 newSig, err := mutate.Signature(gottenSigs2[0], mutate.WithBundle(bund)) 1563 must(err, t) 1564 si2, err = ociremote.SignedEntity(imgRef2, remoteOpts) 1565 must(err, t) 1566 newImage, err := mutate.AttachSignatureToEntity(si2, newSig) 1567 must(err, t) 1568 if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil { 1569 t.Fatal(err) 1570 } 1571 1572 // veriyfing image2 now should fail 1573 mustErr(verify(pubKeyPath, img2, true, nil, ""), t) 1574 } 1575 1576 func TestAttestBlobSignVerify(t *testing.T) { 1577 blob := "someblob" 1578 predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` 1579 predicateType := "slsaprovenance" 1580 1581 td1 := t.TempDir() 1582 t.Cleanup(func() { 1583 os.RemoveAll(td1) 1584 }) 1585 1586 bp := filepath.Join(td1, blob) 1587 if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { 1588 t.Fatal(err) 1589 } 1590 1591 anotherBlob := filepath.Join(td1, "another-blob") 1592 if err := os.WriteFile(anotherBlob, []byte("another-blob"), 0644); err != nil { 1593 t.Fatal(err) 1594 } 1595 1596 predicatePath := filepath.Join(td1, "predicate") 1597 if err := os.WriteFile(predicatePath, []byte(predicate), 0644); err != nil { 1598 t.Fatal(err) 1599 } 1600 1601 outputSignature := filepath.Join(td1, "signature") 1602 1603 _, privKeyPath1, pubKeyPath1 := keypair(t, td1) 1604 1605 ctx := context.Background() 1606 blobVerifyAttestationCmd := cliverify.VerifyBlobAttestationCommand{ 1607 KeyRef: pubKeyPath1, 1608 SignaturePath: outputSignature, 1609 PredicateType: predicateType, 1610 } 1611 // Verify should fail on a bad input 1612 mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t) 1613 1614 // Now attest the blob with the private key 1615 attestBlobCmd := attest.AttestBlobCommand{ 1616 KeyRef: privKeyPath1, 1617 PredicatePath: predicatePath, 1618 PredicateType: predicateType, 1619 OutputSignature: outputSignature, 1620 PassFunc: passFunc, 1621 } 1622 must(attestBlobCmd.Exec(ctx, bp), t) 1623 1624 // Now verify should work 1625 must(blobVerifyAttestationCmd.Exec(ctx, bp), t) 1626 1627 // Make sure we fail with the wrong predicate type 1628 blobVerifyAttestationCmd.PredicateType = "custom" 1629 mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t) 1630 1631 // Make sure we fail with the wrong blob (set the predicate type back) 1632 blobVerifyAttestationCmd.PredicateType = predicateType 1633 mustErr(blobVerifyAttestationCmd.Exec(ctx, anotherBlob), t) 1634 }