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