github.com/goreleaser/goreleaser@v1.25.1/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/goreleaser/goreleaser/internal/artifact" 18 "github.com/goreleaser/goreleaser/internal/git" 19 "github.com/goreleaser/goreleaser/internal/skips" 20 "github.com/goreleaser/goreleaser/internal/testctx" 21 "github.com/goreleaser/goreleaser/internal/testlib" 22 "github.com/goreleaser/goreleaser/internal/tmpl" 23 "github.com/goreleaser/goreleaser/pkg/config" 24 "github.com/goreleaser/goreleaser/pkg/context" 25 "github.com/stretchr/testify/require" 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 code := m.Run() 50 _ = os.RemoveAll(keyring) 51 os.Exit(code) 52 } 53 54 func TestDescription(t *testing.T) { 55 require.NotEmpty(t, Pipe{}.String()) 56 } 57 58 func TestSignDefault(t *testing.T) { 59 _ = testlib.Mktmp(t) 60 testlib.GitInit(t) 61 62 ctx := testctx.NewWithCfg(config.Project{ 63 Signs: []config.Sign{{}}, 64 }) 65 setGpg(t, ctx, "") // force empty gpg.program 66 67 require.NoError(t, Pipe{}.Default(ctx)) 68 require.Equal(t, "gpg", ctx.Config.Signs[0].Cmd) 69 require.Equal(t, "${artifact}.sig", ctx.Config.Signs[0].Signature) 70 require.Equal(t, []string{"--output", "$signature", "--detach-sig", "$artifact"}, ctx.Config.Signs[0].Args) 71 require.Equal(t, "none", ctx.Config.Signs[0].Artifacts) 72 } 73 74 func TestDefaultGpgFromGitConfig(t *testing.T) { 75 _ = testlib.Mktmp(t) 76 testlib.GitInit(t) 77 78 ctx := testctx.NewWithCfg(config.Project{ 79 Signs: []config.Sign{{}}, 80 }) 81 setGpg(t, ctx, "not-really-gpg") 82 83 require.NoError(t, Pipe{}.Default(ctx)) 84 require.Equal(t, "not-really-gpg", ctx.Config.Signs[0].Cmd) 85 } 86 87 func TestSignDisabled(t *testing.T) { 88 ctx := testctx.NewWithCfg(config.Project{Signs: []config.Sign{{Artifacts: "none"}}}) 89 err := Pipe{}.Run(ctx) 90 require.EqualError(t, err, "artifact signing is disabled") 91 } 92 93 func TestSignInvalidArtifacts(t *testing.T) { 94 ctx := testctx.NewWithCfg(config.Project{Signs: []config.Sign{{Artifacts: "foo"}}}) 95 err := Pipe{}.Run(ctx) 96 require.EqualError(t, err, "invalid list of artifacts to sign: foo") 97 } 98 99 func TestSignArtifacts(t *testing.T) { 100 stdin := passwordUser 101 tmplStdin := passwordUserTmpl 102 tests := []struct { 103 desc string 104 ctx *context.Context 105 signaturePaths []string 106 signatureNames []string 107 certificateNames []string 108 expectedErrMsg string 109 expectedErrIs error 110 expectedErrAs any 111 user string 112 }{ 113 { 114 desc: "sign cmd not found", 115 expectedErrIs: exec.ErrNotFound, 116 ctx: testctx.NewWithCfg(config.Project{ 117 Signs: []config.Sign{ 118 { 119 Artifacts: "all", 120 Cmd: "not-a-valid-cmd", 121 }, 122 }, 123 }), 124 }, 125 { 126 desc: "sign errors", 127 expectedErrMsg: "sign: exit failed", 128 ctx: testctx.NewWithCfg(config.Project{ 129 Signs: []config.Sign{ 130 { 131 Artifacts: "all", 132 Cmd: "exit", 133 Args: []string{"1"}, 134 }, 135 }, 136 }), 137 }, 138 { 139 desc: "invalid certificate template", 140 expectedErrAs: &tmpl.Error{}, 141 ctx: testctx.NewWithCfg(config.Project{ 142 Signs: []config.Sign{ 143 { 144 Artifacts: "all", 145 Cmd: "exit", 146 Certificate: "{{ .blah }}", 147 }, 148 }, 149 }), 150 }, 151 { 152 desc: "invalid signature template", 153 expectedErrAs: &tmpl.Error{}, 154 ctx: testctx.NewWithCfg(config.Project{ 155 Signs: []config.Sign{ 156 { 157 Artifacts: "all", 158 Cmd: "exit", 159 Signature: "{{ .blah }}", 160 }, 161 }, 162 }), 163 }, 164 { 165 desc: "invalid args template", 166 expectedErrAs: &tmpl.Error{}, 167 ctx: testctx.NewWithCfg(config.Project{ 168 Signs: []config.Sign{ 169 { 170 Artifacts: "all", 171 Cmd: "exit", 172 Args: []string{"${FOO}-{{ .foo }{{}}{"}, 173 }, 174 }, 175 Env: []string{ 176 "FOO=BAR", 177 }, 178 }), 179 }, 180 { 181 desc: "invalid env template", 182 expectedErrAs: &tmpl.Error{}, 183 ctx: testctx.NewWithCfg(config.Project{ 184 Signs: []config.Sign{ 185 { 186 Artifacts: "all", 187 Cmd: "exit", 188 Env: []string{"A={{ .blah }}"}, 189 }, 190 }, 191 }), 192 }, 193 { 194 desc: "sign all artifacts", 195 ctx: testctx.NewWithCfg(config.Project{ 196 Signs: []config.Sign{ 197 { 198 Artifacts: "all", 199 }, 200 }, 201 }), 202 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"}, 203 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"}, 204 }, 205 { 206 desc: "sign archives", 207 ctx: testctx.NewWithCfg(config.Project{ 208 Signs: []config.Sign{ 209 { 210 Artifacts: "archive", 211 }, 212 }, 213 }), 214 signaturePaths: []string{"artifact1.sig", "artifact2.sig"}, 215 signatureNames: []string{"artifact1.sig", "artifact2.sig"}, 216 }, 217 { 218 desc: "sign packages", 219 ctx: testctx.NewWithCfg(config.Project{ 220 Signs: []config.Sign{ 221 { 222 Artifacts: "package", 223 }, 224 }, 225 }), 226 signaturePaths: []string{"package1.deb.sig"}, 227 signatureNames: []string{"package1.deb.sig"}, 228 }, 229 { 230 desc: "sign binaries", 231 ctx: testctx.NewWithCfg(config.Project{ 232 Signs: []config.Sign{ 233 { 234 Artifacts: "binary", 235 }, 236 }, 237 }), 238 signaturePaths: []string{"artifact3.sig", "linux_amd64/artifact4.sig"}, 239 signatureNames: []string{"artifact3_1.0.0_linux_amd64.sig", "artifact4_1.0.0_linux_amd64.sig"}, 240 }, 241 { 242 desc: "multiple sign configs", 243 ctx: testctx.NewWithCfg(config.Project{ 244 Env: []string{ 245 "GPG_KEY_ID=" + fakeGPGKeyID, 246 }, 247 Signs: []config.Sign{ 248 { 249 ID: "s1", 250 Artifacts: "checksum", 251 }, 252 { 253 ID: "s2", 254 Artifacts: "archive", 255 Signature: "${artifact}.{{ .Env.GPG_KEY_ID }}.sig", 256 }, 257 }, 258 }), 259 signaturePaths: []string{ 260 "artifact1." + fakeGPGKeyID + ".sig", 261 "artifact2." + fakeGPGKeyID + ".sig", 262 "checksum.sig", 263 "checksum2.sig", 264 }, 265 signatureNames: []string{ 266 "artifact1." + fakeGPGKeyID + ".sig", 267 "artifact2." + fakeGPGKeyID + ".sig", 268 "checksum.sig", 269 "checksum2.sig", 270 }, 271 }, 272 { 273 desc: "sign filtered artifacts", 274 ctx: testctx.NewWithCfg(config.Project{ 275 Signs: []config.Sign{ 276 { 277 Artifacts: "all", 278 IDs: []string{"foo"}, 279 }, 280 }, 281 }), 282 signaturePaths: []string{"artifact1.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "artifact5.tar.gz.sig", "package1.deb.sig"}, 283 signatureNames: []string{"artifact1.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact5.tar.gz.sig", "package1.deb.sig"}, 284 }, 285 { 286 desc: "sign only checksums", 287 ctx: testctx.NewWithCfg(config.Project{ 288 Signs: []config.Sign{ 289 { 290 Artifacts: "checksum", 291 }, 292 }, 293 }), 294 signaturePaths: []string{"checksum.sig", "checksum2.sig"}, 295 signatureNames: []string{"checksum.sig", "checksum2.sig"}, 296 }, 297 { 298 desc: "sign only filtered checksums", 299 ctx: testctx.NewWithCfg(config.Project{ 300 Signs: []config.Sign{ 301 { 302 Artifacts: "checksum", 303 IDs: []string{"foo"}, 304 }, 305 }, 306 }), 307 signaturePaths: []string{"checksum.sig", "checksum2.sig"}, 308 signatureNames: []string{"checksum.sig", "checksum2.sig"}, 309 }, 310 { 311 desc: "sign only source", 312 ctx: testctx.NewWithCfg(config.Project{ 313 Signs: []config.Sign{ 314 { 315 Artifacts: "source", 316 }, 317 }, 318 }), 319 signaturePaths: []string{"artifact5.tar.gz.sig"}, 320 signatureNames: []string{"artifact5.tar.gz.sig"}, 321 }, 322 { 323 desc: "sign only source filter by id", 324 ctx: testctx.NewWithCfg(config.Project{ 325 Signs: []config.Sign{ 326 { 327 Artifacts: "source", 328 IDs: []string{"should-not-be-used"}, 329 }, 330 }, 331 }), 332 signaturePaths: []string{"artifact5.tar.gz.sig"}, 333 signatureNames: []string{"artifact5.tar.gz.sig"}, 334 }, 335 { 336 desc: "sign only sbom", 337 ctx: testctx.NewWithCfg(config.Project{ 338 Signs: []config.Sign{ 339 { 340 Artifacts: "sbom", 341 }, 342 }, 343 }), 344 signaturePaths: []string{"artifact5.tar.gz.sbom.sig"}, 345 signatureNames: []string{"artifact5.tar.gz.sbom.sig"}, 346 }, 347 { 348 desc: "sign all artifacts with env", 349 ctx: testctx.NewWithCfg(config.Project{ 350 Signs: []config.Sign{ 351 { 352 Artifacts: "all", 353 Args: []string{ 354 "-u", 355 "${TEST_USER}", 356 "--output", 357 "${signature}", 358 "--detach-sign", 359 "${artifact}", 360 }, 361 }, 362 }, 363 Env: []string{ 364 fmt.Sprintf("TEST_USER=%s", user), 365 }, 366 }), 367 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"}, 368 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"}, 369 }, 370 { 371 desc: "sign all artifacts with template", 372 ctx: testctx.NewWithCfg(config.Project{ 373 Signs: []config.Sign{ 374 { 375 Artifacts: "all", 376 Args: []string{ 377 "-u", 378 "{{ .Env.SOME_TEST_USER }}", 379 "--output", 380 "${signature}", 381 "--detach-sign", 382 "${artifact}", 383 }, 384 }, 385 }, 386 Env: []string{ 387 fmt.Sprintf("SOME_TEST_USER=%s", user), 388 }, 389 }), 390 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"}, 391 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"}, 392 }, 393 { 394 desc: "sign single with password from stdin", 395 ctx: testctx.NewWithCfg(config.Project{ 396 Signs: []config.Sign{ 397 { 398 Artifacts: "all", 399 Args: []string{ 400 "-u", 401 passwordUser, 402 "--batch", 403 "--pinentry-mode", 404 "loopback", 405 "--passphrase-fd", 406 "0", 407 "--output", 408 "${signature}", 409 "--detach-sign", 410 "${artifact}", 411 }, 412 Stdin: &stdin, 413 }, 414 }, 415 }), 416 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"}, 417 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"}, 418 user: passwordUser, 419 }, 420 { 421 desc: "sign single with password from templated stdin", 422 ctx: testctx.NewWithCfg(config.Project{ 423 Env: []string{"GPG_PASSWORD=" + stdin}, 424 Signs: []config.Sign{ 425 { 426 Artifacts: "all", 427 Args: []string{ 428 "-u", 429 passwordUser, 430 "--batch", 431 "--pinentry-mode", 432 "loopback", 433 "--passphrase-fd", 434 "0", 435 "--output", 436 "${signature}", 437 "--detach-sign", 438 "${artifact}", 439 }, 440 Stdin: &tmplStdin, 441 }, 442 }, 443 }), 444 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"}, 445 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"}, 446 user: passwordUser, 447 }, 448 { 449 desc: "sign single with password from stdin_file", 450 ctx: testctx.NewWithCfg(config.Project{ 451 Signs: []config.Sign{ 452 { 453 Artifacts: "all", 454 Args: []string{ 455 "-u", 456 passwordUser, 457 "--batch", 458 "--pinentry-mode", 459 "loopback", 460 "--passphrase-fd", 461 "0", 462 "--output", 463 "${signature}", 464 "--detach-sign", 465 "${artifact}", 466 }, 467 StdinFile: filepath.Join(keyring, passwordUser), 468 }, 469 }, 470 }), 471 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"}, 472 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"}, 473 user: passwordUser, 474 }, 475 { 476 desc: "missing stdin_file", 477 ctx: testctx.NewWithCfg(config.Project{ 478 Signs: []config.Sign{ 479 { 480 Artifacts: "all", 481 Args: []string{ 482 "--batch", 483 "--pinentry-mode", 484 "loopback", 485 "--passphrase-fd", 486 "0", 487 }, 488 StdinFile: "/tmp/non-existing-file", 489 }, 490 }, 491 }), 492 expectedErrIs: os.ErrNotExist, 493 }, 494 { 495 desc: "sign creating certificate", 496 ctx: testctx.NewWithCfg(config.Project{ 497 Signs: []config.Sign{ 498 { 499 Certificate: "${artifact}.pem", 500 Artifacts: "checksum", 501 }, 502 }, 503 }), 504 signaturePaths: []string{"checksum.sig", "checksum2.sig"}, 505 signatureNames: []string{"checksum.sig", "checksum2.sig"}, 506 certificateNames: []string{"checksum.pem", "checksum2.pem"}, 507 }, 508 { 509 desc: "sign all artifacts with env and certificate", 510 ctx: testctx.NewWithCfg(config.Project{ 511 Signs: []config.Sign{ 512 { 513 Env: []string{"NOT_HONK=honk", "HONK={{ .Env.NOT_HONK }}"}, 514 Certificate: `{{ trimsuffix (trimsuffix .Env.artifact ".tar.gz") ".deb" }}_${HONK}.pem`, 515 Artifacts: "all", 516 }, 517 }, 518 }), 519 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"}, 520 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"}, 521 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"}, 522 }, 523 } 524 525 for _, test := range tests { 526 if test.user == "" { 527 test.user = user 528 } 529 530 t.Run(test.desc, func(t *testing.T) { 531 testSign( 532 t, 533 test.ctx, 534 test.certificateNames, 535 test.signaturePaths, 536 test.signatureNames, 537 test.user, 538 test.expectedErrMsg, 539 test.expectedErrIs, 540 test.expectedErrAs, 541 ) 542 }) 543 } 544 } 545 546 func testSign( 547 tb testing.TB, 548 ctx *context.Context, 549 certificateNames, signaturePaths, signatureNames []string, 550 user, expectedErrMsg string, 551 expectedErrIs error, 552 expectedErrAs any, 553 ) { 554 tb.Helper() 555 tmpdir := tb.TempDir() 556 557 ctx.Config.Dist = tmpdir 558 559 // create some fake artifacts 560 artifacts := []string{"artifact1", "artifact2", "artifact3", "checksum", "checksum2", "package1.deb"} 561 require.NoError(tb, os.Mkdir(filepath.Join(tmpdir, "linux_amd64"), os.ModePerm)) 562 for _, f := range artifacts { 563 file := filepath.Join(tmpdir, f) 564 require.NoError(tb, os.WriteFile(file, []byte("foo"), 0o644)) 565 } 566 require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "linux_amd64", "artifact4"), []byte("foo"), 0o644)) 567 artifacts = append(artifacts, "linux_amd64/artifact4") 568 require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz"), []byte("foo"), 0o644)) 569 artifacts = append(artifacts, "artifact5.tar.gz") 570 require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz.sbom"), []byte("sbom(foo)"), 0o644)) 571 artifacts = append(artifacts, "artifact5.tar.gz.sbom") 572 ctx.Artifacts.Add(&artifact.Artifact{ 573 Name: "artifact1", 574 Path: filepath.Join(tmpdir, "artifact1"), 575 Type: artifact.UploadableArchive, 576 Extra: map[string]interface{}{ 577 artifact.ExtraID: "foo", 578 }, 579 }) 580 ctx.Artifacts.Add(&artifact.Artifact{ 581 Name: "artifact2", 582 Path: filepath.Join(tmpdir, "artifact2"), 583 Type: artifact.UploadableArchive, 584 Extra: map[string]interface{}{ 585 artifact.ExtraID: "foo3", 586 }, 587 }) 588 ctx.Artifacts.Add(&artifact.Artifact{ 589 Name: "artifact3_1.0.0_linux_amd64", 590 Path: filepath.Join(tmpdir, "artifact3"), 591 Type: artifact.UploadableBinary, 592 Extra: map[string]interface{}{ 593 artifact.ExtraID: "foo", 594 }, 595 }) 596 ctx.Artifacts.Add(&artifact.Artifact{ 597 Name: "checksum", 598 Path: filepath.Join(tmpdir, "checksum"), 599 Type: artifact.Checksum, 600 }) 601 ctx.Artifacts.Add(&artifact.Artifact{ 602 Name: "checksum2", 603 Path: filepath.Join(tmpdir, "checksum2"), 604 Type: artifact.Checksum, 605 Extra: map[string]interface{}{ 606 "Refresh": func() error { 607 file := filepath.Join(tmpdir, "checksum2") 608 return os.WriteFile(file, []byte("foo"), 0o644) 609 }, 610 }, 611 }) 612 ctx.Artifacts.Add(&artifact.Artifact{ 613 Name: "artifact4_1.0.0_linux_amd64", 614 Path: filepath.Join(tmpdir, "linux_amd64", "artifact4"), 615 Type: artifact.UploadableBinary, 616 Extra: map[string]interface{}{ 617 artifact.ExtraID: "foo3", 618 }, 619 }) 620 ctx.Artifacts.Add(&artifact.Artifact{ 621 Name: "artifact5.tar.gz", 622 Path: filepath.Join(tmpdir, "artifact5.tar.gz"), 623 Type: artifact.UploadableSourceArchive, 624 }) 625 ctx.Artifacts.Add(&artifact.Artifact{ 626 Name: "artifact5.tar.gz.sbom", 627 Path: filepath.Join(tmpdir, "artifact5.tar.gz.sbom"), 628 Type: artifact.SBOM, 629 }) 630 ctx.Artifacts.Add(&artifact.Artifact{ 631 Name: "package1.deb", 632 Path: filepath.Join(tmpdir, "package1.deb"), 633 Type: artifact.LinuxPackage, 634 Extra: map[string]interface{}{ 635 artifact.ExtraID: "foo", 636 }, 637 }) 638 639 // configure the pipeline 640 // make sure we are using the test keyring 641 require.NoError(tb, Pipe{}.Default(ctx)) 642 for i := range ctx.Config.Signs { 643 ctx.Config.Signs[i].Args = append( 644 []string{"--homedir", keyring}, 645 ctx.Config.Signs[i].Args..., 646 ) 647 } 648 649 err := Pipe{}.Run(ctx) 650 651 // run the pipeline 652 if expectedErrMsg != "" { 653 require.ErrorContains(tb, err, 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 }