github.com/triarius/goreleaser@v1.12.5/internal/pipe/sign/sign_test.go (about) 1 package sign 2 3 import ( 4 "bytes" 5 "fmt" 6 "math/rand" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "sort" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/assert" 16 17 "github.com/triarius/goreleaser/internal/artifact" 18 "github.com/triarius/goreleaser/pkg/config" 19 "github.com/triarius/goreleaser/pkg/context" 20 "github.com/stretchr/testify/require" 21 ) 22 23 var ( 24 originKeyring = "testdata/gnupg" 25 keyring string 26 ) 27 28 const ( 29 user = "nopass" 30 passwordUser = "password" 31 passwordUserTmpl = "{{ .Env.GPG_PASSWORD }}" 32 fakeGPGKeyID = "23E7505E" 33 ) 34 35 func TestMain(m *testing.M) { 36 rand.Seed(time.Now().UnixNano()) 37 keyring = fmt.Sprintf("/tmp/gorel_gpg_test.%d", rand.Int()) 38 fmt.Println("copying", originKeyring, "to", keyring) 39 if err := exec.Command("cp", "-Rf", originKeyring, keyring).Run(); err != nil { 40 fmt.Printf("failed to copy %s to %s: %s", originKeyring, keyring, err) 41 os.Exit(1) 42 } 43 44 defer os.RemoveAll(keyring) 45 os.Exit(m.Run()) 46 } 47 48 func TestDescription(t *testing.T) { 49 require.NotEmpty(t, Pipe{}.String()) 50 } 51 52 func TestSignDefault(t *testing.T) { 53 ctx := &context.Context{ 54 Config: config.Project{ 55 Signs: []config.Sign{{}}, 56 }, 57 } 58 err := Pipe{}.Default(ctx) 59 require.NoError(t, err) 60 require.Equal(t, ctx.Config.Signs[0].Cmd, "gpg") 61 require.Equal(t, ctx.Config.Signs[0].Signature, "${artifact}.sig") 62 require.Equal(t, ctx.Config.Signs[0].Args, []string{"--output", "$signature", "--detach-sig", "$artifact"}) 63 require.Equal(t, ctx.Config.Signs[0].Artifacts, "none") 64 } 65 66 func TestSignDisabled(t *testing.T) { 67 ctx := context.New(config.Project{}) 68 ctx.Config.Signs = []config.Sign{ 69 {Artifacts: "none"}, 70 } 71 err := Pipe{}.Run(ctx) 72 require.EqualError(t, err, "artifact signing is disabled") 73 } 74 75 func TestSignInvalidArtifacts(t *testing.T) { 76 ctx := context.New(config.Project{}) 77 ctx.Config.Signs = []config.Sign{ 78 {Artifacts: "foo"}, 79 } 80 err := Pipe{}.Run(ctx) 81 require.EqualError(t, err, "invalid list of artifacts to sign: foo") 82 } 83 84 func TestSignArtifacts(t *testing.T) { 85 stdin := passwordUser 86 tmplStdin := passwordUserTmpl 87 tests := []struct { 88 desc string 89 ctx *context.Context 90 signaturePaths []string 91 signatureNames []string 92 certificateNames []string 93 expectedErrMsg string 94 user string 95 }{ 96 { 97 desc: "sign cmd not found", 98 expectedErrMsg: `sign: not-a-valid-cmd failed: exec: "not-a-valid-cmd": executable file not found in $PATH: `, 99 ctx: context.New( 100 config.Project{ 101 Signs: []config.Sign{ 102 { 103 Artifacts: "all", 104 Cmd: "not-a-valid-cmd", 105 }, 106 }, 107 }, 108 ), 109 }, 110 { 111 desc: "sign errors", 112 expectedErrMsg: "sign: exit failed", 113 ctx: context.New( 114 config.Project{ 115 Signs: []config.Sign{ 116 { 117 Artifacts: "all", 118 Cmd: "exit", 119 Args: []string{"1"}, 120 }, 121 }, 122 }, 123 ), 124 }, 125 { 126 desc: "invalid certificate template", 127 expectedErrMsg: `sign failed: artifact1: template: tmpl:1:3: executing "tmpl" at <.blah>: map has no entry for key "blah"`, 128 ctx: context.New( 129 config.Project{ 130 Signs: []config.Sign{ 131 { 132 Artifacts: "all", 133 Cmd: "exit", 134 Certificate: "{{ .blah }}", 135 }, 136 }, 137 }, 138 ), 139 }, 140 { 141 desc: "invalid signature template", 142 expectedErrMsg: `sign failed: artifact1: template: tmpl:1:3: executing "tmpl" at <.blah>: map has no entry for key "blah"`, 143 ctx: context.New( 144 config.Project{ 145 Signs: []config.Sign{ 146 { 147 Artifacts: "all", 148 Cmd: "exit", 149 Signature: "{{ .blah }}", 150 }, 151 }, 152 }, 153 ), 154 }, 155 { 156 desc: "invalid args template", 157 expectedErrMsg: `sign failed: artifact1: template: tmpl:1: unexpected "}" in operand`, 158 ctx: context.New( 159 config.Project{ 160 Signs: []config.Sign{ 161 { 162 Artifacts: "all", 163 Cmd: "exit", 164 Args: []string{"${FOO}-{{ .foo }{{}}{"}, 165 }, 166 }, 167 Env: []string{ 168 "FOO=BAR", 169 }, 170 }, 171 ), 172 }, 173 { 174 desc: "invalid env template", 175 expectedErrMsg: `sign failed: artifact1: template: tmpl:1:5: executing "tmpl" at <.blah>: map has no entry for key "blah"`, 176 ctx: context.New( 177 config.Project{ 178 Signs: []config.Sign{ 179 { 180 Artifacts: "all", 181 Cmd: "exit", 182 Env: []string{"A={{ .blah }}"}, 183 }, 184 }, 185 }, 186 ), 187 }, 188 { 189 desc: "sign all artifacts", 190 ctx: context.New( 191 config.Project{ 192 Signs: []config.Sign{ 193 { 194 Artifacts: "all", 195 }, 196 }, 197 }, 198 ), 199 signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 200 signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 201 }, 202 { 203 desc: "sign archives", 204 ctx: context.New( 205 config.Project{ 206 Signs: []config.Sign{ 207 { 208 Artifacts: "archive", 209 }, 210 }, 211 }, 212 ), 213 signaturePaths: []string{"artifact1.sig", "artifact2.sig"}, 214 signatureNames: []string{"artifact1.sig", "artifact2.sig"}, 215 }, 216 { 217 desc: "sign packages", 218 ctx: context.New( 219 config.Project{ 220 Signs: []config.Sign{ 221 { 222 Artifacts: "package", 223 }, 224 }, 225 }, 226 ), 227 signaturePaths: []string{"package1.deb.sig"}, 228 signatureNames: []string{"package1.deb.sig"}, 229 }, 230 { 231 desc: "sign binaries", 232 ctx: context.New( 233 config.Project{ 234 Signs: []config.Sign{ 235 { 236 Artifacts: "binary", 237 }, 238 }, 239 }, 240 ), 241 signaturePaths: []string{"artifact3.sig", "linux_amd64/artifact4.sig"}, 242 signatureNames: []string{"artifact3_1.0.0_linux_amd64.sig", "artifact4_1.0.0_linux_amd64.sig"}, 243 }, 244 { 245 desc: "multiple sign configs", 246 ctx: context.New( 247 config.Project{ 248 Env: []string{ 249 "GPG_KEY_ID=" + fakeGPGKeyID, 250 }, 251 Signs: []config.Sign{ 252 { 253 ID: "s1", 254 Artifacts: "checksum", 255 }, 256 { 257 ID: "s2", 258 Artifacts: "archive", 259 Signature: "${artifact}.{{ .Env.GPG_KEY_ID }}.sig", 260 }, 261 }, 262 }, 263 ), 264 signaturePaths: []string{ 265 "artifact1." + fakeGPGKeyID + ".sig", 266 "artifact2." + fakeGPGKeyID + ".sig", 267 "checksum.sig", 268 "checksum2.sig", 269 }, 270 signatureNames: []string{ 271 "artifact1." + fakeGPGKeyID + ".sig", 272 "artifact2." + fakeGPGKeyID + ".sig", 273 "checksum.sig", 274 "checksum2.sig", 275 }, 276 }, 277 { 278 desc: "sign filtered artifacts", 279 ctx: context.New( 280 config.Project{ 281 Signs: []config.Sign{ 282 { 283 Artifacts: "all", 284 IDs: []string{"foo"}, 285 }, 286 }, 287 }, 288 ), 289 signaturePaths: []string{"artifact1.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "artifact5.tar.gz.sig", "package1.deb.sig"}, 290 signatureNames: []string{"artifact1.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact5.tar.gz.sig", "package1.deb.sig"}, 291 }, 292 { 293 desc: "sign only checksums", 294 ctx: context.New( 295 config.Project{ 296 Signs: []config.Sign{ 297 { 298 Artifacts: "checksum", 299 }, 300 }, 301 }, 302 ), 303 signaturePaths: []string{"checksum.sig", "checksum2.sig"}, 304 signatureNames: []string{"checksum.sig", "checksum2.sig"}, 305 }, 306 { 307 desc: "sign only filtered checksums", 308 ctx: context.New( 309 config.Project{ 310 Signs: []config.Sign{ 311 { 312 Artifacts: "checksum", 313 IDs: []string{"foo"}, 314 }, 315 }, 316 }, 317 ), 318 signaturePaths: []string{"checksum.sig", "checksum2.sig"}, 319 signatureNames: []string{"checksum.sig", "checksum2.sig"}, 320 }, 321 { 322 desc: "sign only source", 323 ctx: context.New( 324 config.Project{ 325 Signs: []config.Sign{ 326 { 327 Artifacts: "source", 328 }, 329 }, 330 }, 331 ), 332 signaturePaths: []string{"artifact5.tar.gz.sig"}, 333 signatureNames: []string{"artifact5.tar.gz.sig"}, 334 }, 335 { 336 desc: "sign only source filter by id", 337 ctx: context.New( 338 config.Project{ 339 Signs: []config.Sign{ 340 { 341 Artifacts: "source", 342 IDs: []string{"should-not-be-used"}, 343 }, 344 }, 345 }, 346 ), 347 signaturePaths: []string{"artifact5.tar.gz.sig"}, 348 signatureNames: []string{"artifact5.tar.gz.sig"}, 349 }, 350 { 351 desc: "sign only sbom", 352 ctx: context.New( 353 config.Project{ 354 Signs: []config.Sign{ 355 { 356 Artifacts: "sbom", 357 }, 358 }, 359 }, 360 ), 361 signaturePaths: []string{"artifact5.tar.gz.sbom.sig"}, 362 signatureNames: []string{"artifact5.tar.gz.sbom.sig"}, 363 }, 364 { 365 desc: "sign all artifacts with env", 366 ctx: context.New( 367 config.Project{ 368 Signs: []config.Sign{ 369 { 370 Artifacts: "all", 371 Args: []string{ 372 "-u", 373 "${TEST_USER}", 374 "--output", 375 "${signature}", 376 "--detach-sign", 377 "${artifact}", 378 }, 379 }, 380 }, 381 Env: []string{ 382 fmt.Sprintf("TEST_USER=%s", user), 383 }, 384 }, 385 ), 386 signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 387 signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 388 }, 389 { 390 desc: "sign all artifacts with template", 391 ctx: context.New( 392 config.Project{ 393 Signs: []config.Sign{ 394 { 395 Artifacts: "all", 396 Args: []string{ 397 "-u", 398 "{{ .Env.SOME_TEST_USER }}", 399 "--output", 400 "${signature}", 401 "--detach-sign", 402 "${artifact}", 403 }, 404 }, 405 }, 406 Env: []string{ 407 fmt.Sprintf("SOME_TEST_USER=%s", user), 408 }, 409 }, 410 ), 411 signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 412 signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 413 }, 414 { 415 desc: "sign single with password from stdin", 416 ctx: context.New( 417 config.Project{ 418 Signs: []config.Sign{ 419 { 420 Artifacts: "all", 421 Args: []string{ 422 "-u", 423 passwordUser, 424 "--batch", 425 "--pinentry-mode", 426 "loopback", 427 "--passphrase-fd", 428 "0", 429 "--output", 430 "${signature}", 431 "--detach-sign", 432 "${artifact}", 433 }, 434 Stdin: &stdin, 435 }, 436 }, 437 }, 438 ), 439 signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 440 signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 441 user: passwordUser, 442 }, 443 { 444 desc: "sign single with password from templated stdin", 445 ctx: context.New( 446 config.Project{ 447 Env: []string{"GPG_PASSWORD=" + stdin}, 448 Signs: []config.Sign{ 449 { 450 Artifacts: "all", 451 Args: []string{ 452 "-u", 453 passwordUser, 454 "--batch", 455 "--pinentry-mode", 456 "loopback", 457 "--passphrase-fd", 458 "0", 459 "--output", 460 "${signature}", 461 "--detach-sign", 462 "${artifact}", 463 }, 464 Stdin: &tmplStdin, 465 }, 466 }, 467 }, 468 ), 469 signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 470 signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 471 user: passwordUser, 472 }, 473 { 474 desc: "sign single with password from stdin_file", 475 ctx: context.New( 476 config.Project{ 477 Signs: []config.Sign{ 478 { 479 Artifacts: "all", 480 Args: []string{ 481 "-u", 482 passwordUser, 483 "--batch", 484 "--pinentry-mode", 485 "loopback", 486 "--passphrase-fd", 487 "0", 488 "--output", 489 "${signature}", 490 "--detach-sign", 491 "${artifact}", 492 }, 493 StdinFile: filepath.Join(keyring, passwordUser), 494 }, 495 }, 496 }, 497 ), 498 signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 499 signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 500 user: passwordUser, 501 }, 502 { 503 desc: "missing stdin_file", 504 ctx: context.New( 505 config.Project{ 506 Signs: []config.Sign{ 507 { 508 Artifacts: "all", 509 Args: []string{ 510 "--batch", 511 "--pinentry-mode", 512 "loopback", 513 "--passphrase-fd", 514 "0", 515 }, 516 StdinFile: "/tmp/non-existing-file", 517 }, 518 }, 519 }, 520 ), 521 expectedErrMsg: `sign failed: cannot open file /tmp/non-existing-file: open /tmp/non-existing-file: no such file or directory`, 522 }, 523 { 524 desc: "sign creating certificate", 525 ctx: context.New( 526 config.Project{ 527 Signs: []config.Sign{ 528 { 529 Certificate: "${artifact}.pem", 530 Artifacts: "checksum", 531 }, 532 }, 533 }, 534 ), 535 signaturePaths: []string{"checksum.sig", "checksum2.sig"}, 536 signatureNames: []string{"checksum.sig", "checksum2.sig"}, 537 certificateNames: []string{"checksum.pem", "checksum2.pem"}, 538 }, 539 { 540 desc: "sign all artifacts with env and certificate", 541 ctx: context.New( 542 config.Project{ 543 Signs: []config.Sign{ 544 { 545 Env: []string{"NOT_HONK=honk", "HONK={{ .Env.NOT_HONK }}"}, 546 Certificate: `{{ trimsuffix (trimsuffix .Env.artifact ".tar.gz") ".deb" }}_${HONK}.pem`, 547 Artifacts: "all", 548 }, 549 }, 550 }, 551 ), 552 signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 553 signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"}, 554 certificateNames: []string{"artifact1_honk.pem", "artifact2_honk.pem", "artifact3_1.0.0_linux_amd64_honk.pem", "checksum_honk.pem", "checksum2_honk.pem", "artifact4_1.0.0_linux_amd64_honk.pem", "artifact5_honk.pem", "artifact5.tar.gz.sbom_honk.pem", "package1_honk.pem"}, 555 }, 556 } 557 558 for _, test := range tests { 559 if test.user == "" { 560 test.user = user 561 } 562 563 t.Run(test.desc, func(t *testing.T) { 564 testSign(t, test.ctx, test.certificateNames, test.signaturePaths, test.signatureNames, test.user, test.expectedErrMsg) 565 }) 566 } 567 } 568 569 func testSign(tb testing.TB, ctx *context.Context, certificateNames, signaturePaths, signatureNames []string, user, expectedErrMsg string) { 570 tb.Helper() 571 tmpdir := tb.TempDir() 572 573 ctx.Config.Dist = tmpdir 574 575 // create some fake artifacts 576 artifacts := []string{"artifact1", "artifact2", "artifact3", "checksum", "checksum2", "package1.deb"} 577 require.NoError(tb, os.Mkdir(filepath.Join(tmpdir, "linux_amd64"), os.ModePerm)) 578 for _, f := range artifacts { 579 file := filepath.Join(tmpdir, f) 580 require.NoError(tb, os.WriteFile(file, []byte("foo"), 0o644)) 581 } 582 require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "linux_amd64", "artifact4"), []byte("foo"), 0o644)) 583 artifacts = append(artifacts, "linux_amd64/artifact4") 584 require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz"), []byte("foo"), 0o644)) 585 artifacts = append(artifacts, "artifact5.tar.gz") 586 require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz.sbom"), []byte("sbom(foo)"), 0o644)) 587 artifacts = append(artifacts, "artifact5.tar.gz.sbom") 588 ctx.Artifacts.Add(&artifact.Artifact{ 589 Name: "artifact1", 590 Path: filepath.Join(tmpdir, "artifact1"), 591 Type: artifact.UploadableArchive, 592 Extra: map[string]interface{}{ 593 artifact.ExtraID: "foo", 594 }, 595 }) 596 ctx.Artifacts.Add(&artifact.Artifact{ 597 Name: "artifact2", 598 Path: filepath.Join(tmpdir, "artifact2"), 599 Type: artifact.UploadableArchive, 600 Extra: map[string]interface{}{ 601 artifact.ExtraID: "foo3", 602 }, 603 }) 604 ctx.Artifacts.Add(&artifact.Artifact{ 605 Name: "artifact3_1.0.0_linux_amd64", 606 Path: filepath.Join(tmpdir, "artifact3"), 607 Type: artifact.UploadableBinary, 608 Extra: map[string]interface{}{ 609 artifact.ExtraID: "foo", 610 }, 611 }) 612 ctx.Artifacts.Add(&artifact.Artifact{ 613 Name: "checksum", 614 Path: filepath.Join(tmpdir, "checksum"), 615 Type: artifact.Checksum, 616 }) 617 ctx.Artifacts.Add(&artifact.Artifact{ 618 Name: "checksum2", 619 Path: filepath.Join(tmpdir, "checksum2"), 620 Type: artifact.Checksum, 621 Extra: map[string]interface{}{ 622 "Refresh": func() error { 623 file := filepath.Join(tmpdir, "checksum2") 624 return os.WriteFile(file, []byte("foo"), 0o644) 625 }, 626 }, 627 }) 628 ctx.Artifacts.Add(&artifact.Artifact{ 629 Name: "artifact4_1.0.0_linux_amd64", 630 Path: filepath.Join(tmpdir, "linux_amd64", "artifact4"), 631 Type: artifact.UploadableBinary, 632 Extra: map[string]interface{}{ 633 artifact.ExtraID: "foo3", 634 }, 635 }) 636 ctx.Artifacts.Add(&artifact.Artifact{ 637 Name: "artifact5.tar.gz", 638 Path: filepath.Join(tmpdir, "artifact5.tar.gz"), 639 Type: artifact.UploadableSourceArchive, 640 }) 641 ctx.Artifacts.Add(&artifact.Artifact{ 642 Name: "artifact5.tar.gz.sbom", 643 Path: filepath.Join(tmpdir, "artifact5.tar.gz.sbom"), 644 Type: artifact.SBOM, 645 }) 646 ctx.Artifacts.Add(&artifact.Artifact{ 647 Name: "package1.deb", 648 Path: filepath.Join(tmpdir, "package1.deb"), 649 Type: artifact.LinuxPackage, 650 Extra: map[string]interface{}{ 651 artifact.ExtraID: "foo", 652 }, 653 }) 654 655 // configure the pipeline 656 // make sure we are using the test keyring 657 require.NoError(tb, Pipe{}.Default(ctx)) 658 for i := range ctx.Config.Signs { 659 ctx.Config.Signs[i].Args = append( 660 []string{"--homedir", keyring}, 661 ctx.Config.Signs[i].Args..., 662 ) 663 } 664 665 // run the pipeline 666 if expectedErrMsg != "" { 667 err := Pipe{}.Run(ctx) 668 require.Error(tb, err) 669 require.Contains(tb, err.Error(), expectedErrMsg) 670 return 671 } 672 673 require.NoError(tb, Pipe{}.Run(ctx)) 674 675 // ensure all artifacts have an ID 676 for _, arti := range ctx.Artifacts.Filter( 677 artifact.Or( 678 artifact.ByType(artifact.Signature), 679 artifact.ByType(artifact.Certificate), 680 ), 681 ).List() { 682 require.NotEmptyf(tb, arti.ID(), ".Extra.ID on %s", arti.Path) 683 } 684 685 certificates := ctx.Artifacts.Filter(artifact.ByType(artifact.Certificate)).List() 686 certNames := []string{} 687 for _, cert := range certificates { 688 certNames = append(certNames, cert.Name) 689 require.True(tb, strings.HasPrefix(cert.Path, ctx.Config.Dist)) 690 } 691 692 assert.ElementsMatch(tb, certificateNames, certNames) 693 694 // verify that only the artifacts and the signatures are in the dist dir 695 gotFiles := []string{} 696 697 require.NoError(tb, filepath.Walk(tmpdir, 698 func(path string, info os.FileInfo, err error) error { 699 if err != nil { 700 return err 701 } 702 if info.IsDir() { 703 return nil 704 } 705 relPath, err := filepath.Rel(tmpdir, path) 706 if err != nil { 707 return err 708 } 709 gotFiles = append(gotFiles, relPath) 710 return nil 711 }), 712 ) 713 714 wantFiles := append(artifacts, signaturePaths...) 715 sort.Strings(wantFiles) 716 require.ElementsMatch(tb, wantFiles, gotFiles) 717 718 // verify the signatures 719 for _, sig := range signaturePaths { 720 verifySignature(tb, ctx, sig, user) 721 } 722 723 var signArtifacts []string 724 for _, sig := range ctx.Artifacts.Filter(artifact.ByType(artifact.Signature)).List() { 725 signArtifacts = append(signArtifacts, sig.Name) 726 } 727 // check signature is an artifact 728 require.ElementsMatch(tb, signArtifacts, signatureNames) 729 } 730 731 func verifySignature(tb testing.TB, ctx *context.Context, sig string, user string) { 732 tb.Helper() 733 artifact := strings.TrimSuffix(sig, filepath.Ext(sig)) 734 artifact = strings.TrimSuffix(artifact, "."+fakeGPGKeyID) 735 736 // verify signature was made with key for usesr 'nopass' 737 cmd := exec.Command("gpg", "--homedir", keyring, "--verify", filepath.Join(ctx.Config.Dist, sig), filepath.Join(ctx.Config.Dist, artifact)) 738 out, err := cmd.CombinedOutput() 739 require.NoError(tb, err, string(out)) 740 741 // check if the signature matches the user we expect to do this properly we 742 // might need to have either separate keyrings or export the key from the 743 // keyring before we do the verification. For now we punt and look in the 744 // output. 745 if !bytes.Contains(out, []byte(user)) { 746 tb.Fatalf("%s: signature is not from %s: %s", sig, user, string(out)) 747 } 748 } 749 750 func TestSeveralSignsWithTheSameID(t *testing.T) { 751 ctx := &context.Context{ 752 Config: config.Project{ 753 Signs: []config.Sign{ 754 { 755 ID: "a", 756 }, 757 { 758 ID: "a", 759 }, 760 }, 761 }, 762 } 763 require.EqualError(t, Pipe{}.Default(ctx), "found 2 signs with the ID 'a', please fix your config") 764 } 765 766 func TestSkip(t *testing.T) { 767 t.Run("skip", func(t *testing.T) { 768 require.True(t, Pipe{}.Skip(context.New(config.Project{}))) 769 }) 770 771 t.Run("skip sign", func(t *testing.T) { 772 ctx := context.New(config.Project{}) 773 ctx.SkipSign = true 774 require.True(t, Pipe{}.Skip(ctx)) 775 }) 776 777 t.Run("dont skip", func(t *testing.T) { 778 ctx := context.New(config.Project{ 779 Signs: []config.Sign{ 780 {}, 781 }, 782 }) 783 require.False(t, Pipe{}.Skip(ctx)) 784 }) 785 }