sigs.k8s.io/release-sdk@v0.11.1-0.20240417074027-8061fb5e4952/sign/sign_test.go (about) 1 /* 2 Copyright 2022 The Kubernetes 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 17 package sign_test 18 19 import ( 20 "context" 21 "errors" 22 "log" 23 "net/http" 24 "os" 25 "path/filepath" 26 "sync" 27 "testing" 28 29 "github.com/google/go-containerregistry/pkg/name" 30 "github.com/google/go-containerregistry/pkg/v1/remote/transport" 31 "github.com/sigstore/cosign/v2/pkg/cosign" 32 "github.com/sigstore/rekor/pkg/generated/models" 33 "github.com/stretchr/testify/require" 34 "sigs.k8s.io/release-sdk/sign" 35 "sigs.k8s.io/release-sdk/sign/signfakes" 36 ) 37 38 var errTest = errors.New("error") 39 40 func TestUploadBlob(t *testing.T) { 41 t.Parallel() 42 43 for _, tc := range []struct { 44 prepare func(*signfakes.FakeImpl) 45 assert func(error) 46 }{ 47 { // Success 48 prepare: func(_ *signfakes.FakeImpl) { 49 }, 50 assert: func(err error) { 51 require.Nil(t, err) 52 }, 53 }, 54 } { 55 mock := &signfakes.FakeImpl{} 56 tc.prepare(mock) 57 58 sut := sign.New(sign.Default()) 59 sut.SetImpl(mock) 60 61 err := sut.UploadBlob("") 62 tc.assert(err) 63 } 64 } 65 66 func TestSignImage(t *testing.T) { 67 t.Parallel() 68 // Some of these tests require a real IDentity token 69 token := "DUMMYTOKEN" 70 71 for _, tc := range []struct { 72 fakeReference *FakeReferenceStub 73 prepare func(*signfakes.FakeImpl) 74 assert func(*sign.SignedObject, error) 75 }{ 76 { // Success 77 fakeReference: &FakeReferenceStub{ 78 image: "gcr.io/fake/honk:99.99.99", 79 registry: "gcr.io", 80 repository: "fake/honk", 81 }, 82 prepare: func(mock *signfakes.FakeImpl) { 83 mock.VerifyImageInternalReturns(&sign.SignedObject{}, nil) 84 mock.SignImageInternalReturns(nil) 85 mock.TokenFromProvidersReturns(token, nil) 86 m := &sync.Map{} 87 m.Store("gcr.io/fake/honk:99.99.99", true) 88 mock.ImagesSignedReturns(m, nil) 89 mock.DigestReturns("sha256:honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a", nil) 90 mock.NewWithContextReturns(&testRoundTripper{}, nil) 91 }, 92 assert: func(obj *sign.SignedObject, err error) { 93 require.NoError(t, err) 94 require.NotNil(t, obj) 95 require.NotEmpty(t, obj.Image().Reference()) 96 require.NotEmpty(t, obj.Image().Digest()) 97 require.NotEmpty(t, obj.Image().Signature()) 98 require.Equal(t, obj.Image().Reference(), "gcr.io/fake/honk:99.99.99") 99 require.Equal(t, obj.Image().Digest(), "sha256:honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a") 100 require.Equal(t, obj.Image().Signature(), "gcr.io/fake/honk:sha256-honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a.sig") 101 }, 102 }, 103 { // Failure on Verify 104 fakeReference: &FakeReferenceStub{ 105 image: "gcr.io/fake/honk:99.99.99", 106 registry: "gcr.io", 107 repository: "fake/honk", 108 }, 109 prepare: func(mock *signfakes.FakeImpl) { 110 m := &sync.Map{} 111 m.Store("gcr.io/fake/honk:99.99.99", true) 112 mock.ImagesSignedReturns(m, nil) 113 mock.VerifyImageInternalReturns(nil, errTest) 114 mock.SignImageInternalReturns(nil) 115 mock.TokenFromProvidersReturns(token, nil) 116 }, 117 assert: func(obj *sign.SignedObject, err error) { 118 require.Error(t, err) 119 require.Nil(t, obj) 120 }, 121 }, 122 { // Failure on Sign 123 fakeReference: &FakeReferenceStub{ 124 image: "gcr.io/fake/honk:99.99.99", 125 registry: "gcr.io", 126 repository: "fake/honk", 127 }, 128 prepare: func(mock *signfakes.FakeImpl) { 129 mock.VerifyImageInternalReturns(&sign.SignedObject{}, nil) 130 mock.SignImageInternalReturns(errTest) 131 mock.TokenFromProvidersReturns(token, nil) 132 }, 133 assert: func(obj *sign.SignedObject, err error) { 134 require.Error(t, err) 135 require.Nil(t, obj) 136 }, 137 }, 138 { // Failure getting identity token 139 fakeReference: &FakeReferenceStub{ 140 image: "gcr.io/fake/honk:99.99.99", 141 registry: "gcr.io", 142 repository: "fake/honk", 143 }, 144 prepare: func(mock *signfakes.FakeImpl) { 145 mock.TokenFromProvidersReturns(token, errTest) 146 }, 147 assert: func(obj *sign.SignedObject, err error) { 148 require.Error(t, err) 149 require.Nil(t, obj) 150 }, 151 }, 152 } { 153 mock := &signfakes.FakeImpl{} 154 mock.ParseReferenceReturns(tc.fakeReference, nil) 155 tc.prepare(mock) 156 157 opts := sign.Default() 158 opts.Verbose = true 159 160 sut := sign.New(opts) 161 sut.SetImpl(mock) 162 163 obj, err := sut.SignImage(tc.fakeReference.image) 164 tc.assert(obj, err) 165 } 166 } 167 168 func TestSignFile(t *testing.T) { 169 t.Parallel() 170 171 opts := sign.Default() 172 opts.PrivateKeyPath = "/dummy/cosign.key" 173 opts.PublicKeyPath = "/dummy/cosign.pub" 174 175 // Create temporary directory for files. 176 tempDir, err := os.MkdirTemp("", "k8s-test-file-") 177 require.Nil(t, err) 178 defer func() { 179 require.Nil(t, os.RemoveAll(tempDir)) 180 }() 181 182 // Create temporary file for test. 183 tempFile := filepath.Join(tempDir, "test-file") 184 require.Nil(t, os.WriteFile(tempFile, []byte("dummy-content"), 0o644)) 185 186 for _, tc := range []struct { 187 path string 188 options *sign.Options 189 prepare func(*signfakes.FakeImpl) 190 assert func(*sign.SignedObject, error) 191 }{ 192 { // Success 193 path: tempFile, 194 options: opts, 195 prepare: func(mock *signfakes.FakeImpl) { 196 mock.VerifyFileInternalReturns(nil) 197 mock.SignFileInternalReturns(nil) 198 }, 199 assert: func(obj *sign.SignedObject, err error) { 200 require.Nil(t, err) 201 require.NotNil(t, obj) 202 require.NotEmpty(t, obj.File().Path()) 203 require.NotEmpty(t, obj.File().CertificatePath()) 204 require.NotEmpty(t, obj.File().SignaturePath()) 205 }, 206 }, 207 { // Success custom sig and cert. 208 path: tempFile, 209 options: &sign.Options{ 210 PrivateKeyPath: opts.PrivateKeyPath, 211 OutputSignaturePath: "/tmp/test-file.sig", 212 OutputCertificatePath: "/tmp/test-file.cert", 213 }, 214 prepare: func(mock *signfakes.FakeImpl) { 215 mock.VerifyFileInternalReturns(nil) 216 mock.SignFileInternalReturns(nil) 217 }, 218 assert: func(obj *sign.SignedObject, err error) { 219 require.Nil(t, err) 220 require.NotNil(t, obj) 221 require.NotEmpty(t, obj.File().Path()) 222 require.NotEmpty(t, obj.File().CertificatePath()) 223 require.NotEmpty(t, obj.File().SignaturePath()) 224 }, 225 }, 226 { // File does not exist. 227 path: "/dummy/test-no-file", 228 options: opts, 229 prepare: func(mock *signfakes.FakeImpl) { 230 mock.PayloadBytesReturns(nil, errTest) 231 }, 232 assert: func(obj *sign.SignedObject, err error) { 233 require.Nil(t, obj) 234 require.ErrorContains(t, err, "file retrieve sha256:") 235 }, 236 }, 237 { // File does can't sign. 238 path: tempFile, 239 options: opts, 240 prepare: func(mock *signfakes.FakeImpl) { 241 mock.SignFileInternalReturns(errTest) 242 }, 243 assert: func(obj *sign.SignedObject, err error) { 244 require.Nil(t, obj) 245 require.ErrorContains(t, err, "sign file:") 246 }, 247 }, 248 { // Default sig and cert file test 249 path: tempFile, 250 options: opts, 251 prepare: func(mock *signfakes.FakeImpl) { 252 mock.VerifyFileInternalReturns(nil) 253 mock.SignFileInternalReturns(nil) 254 }, 255 assert: func(obj *sign.SignedObject, err error) { 256 require.Nil(t, err) 257 258 require.NotNil(t, obj) 259 require.NotEmpty(t, obj.File().Path()) 260 require.NotEmpty(t, obj.File().CertificatePath()) 261 require.NotEmpty(t, obj.File().SignaturePath()) 262 263 require.Equal(t, obj.File().Path()+".cert", obj.File().CertificatePath()) 264 require.Equal(t, obj.File().Path()+".sig", obj.File().SignaturePath()) 265 }, 266 }, 267 { // Verify failed. 268 path: tempFile, 269 options: opts, 270 prepare: func(mock *signfakes.FakeImpl) { 271 mock.VerifyFileInternalReturns(errTest) 272 }, 273 assert: func(obj *sign.SignedObject, err error) { 274 require.Nil(t, obj) 275 require.NotNil(t, err) 276 require.ErrorContains(t, err, "verifying signed file:") 277 }, 278 }, 279 } { 280 mock := &signfakes.FakeImpl{} 281 tc.prepare(mock) 282 283 opts := tc.options 284 opts.Verbose = true 285 286 sut := sign.New(opts) 287 sut.SetImpl(mock) 288 289 obj, err := sut.SignFile(tc.path) 290 tc.assert(obj, err) 291 } 292 } 293 294 func TestVerifyImage(t *testing.T) { 295 t.Parallel() 296 297 for _, tc := range []struct { 298 fakeReference *FakeReferenceStub 299 prepare func(*signfakes.FakeImpl) 300 assert func(*sign.SignedObject, error) 301 }{ 302 { // Success 303 fakeReference: &FakeReferenceStub{ 304 image: "gcr.io/fake/honk:99.99.99", 305 registry: "gcr.io", 306 repository: "fake/honk", 307 }, 308 prepare: func(mock *signfakes.FakeImpl) { 309 mock.VerifyImageInternalReturns(&sign.SignedObject{}, nil) 310 m := &sync.Map{} 311 m.Store("gcr.io/fake/honk:99.99.99", true) 312 mock.ImagesSignedReturns(m, nil) 313 mock.DigestReturns("sha256:honk69059c8e84bed02f4c4385d432808e2c8055eb5087f7fea74e286b736a", nil) 314 mock.NewWithContextReturns(&testRoundTripper{}, nil) 315 }, 316 assert: func(_ *sign.SignedObject, err error) { 317 require.Nil(t, err) 318 }, 319 }, 320 { // Failure on Verify 321 fakeReference: &FakeReferenceStub{ 322 image: "gcr.io/fake/honk:99.99.99", 323 registry: "gcr.io", 324 repository: "fake/honk", 325 }, 326 prepare: func(mock *signfakes.FakeImpl) { 327 mock.VerifyImageInternalReturns(nil, errTest) 328 mock.SetenvReturns(nil) 329 m := &sync.Map{} 330 m.Store("gcr.io/fake/honk:99.99.99", true) 331 mock.ImagesSignedReturns(m, nil) 332 }, 333 assert: func(obj *sign.SignedObject, err error) { 334 require.NotNil(t, err) 335 require.Nil(t, obj) 336 }, 337 }, 338 { // Skip on no signatures listed 339 fakeReference: &FakeReferenceStub{ 340 image: "gcr.io/fake/honk:99.99.99", 341 registry: "gcr.io", 342 repository: "fake/honk", 343 }, 344 prepare: func(mock *signfakes.FakeImpl) { 345 m := &sync.Map{} 346 m.Store("gcr.io/fake/honk:99.99.99", false) 347 mock.ImagesSignedReturns(m, nil) 348 }, 349 assert: func(obj *sign.SignedObject, err error) { 350 require.Nil(t, err) 351 require.Nil(t, obj) 352 }, 353 }, 354 } { 355 mock := &signfakes.FakeImpl{} 356 mock.ParseReferenceReturns(tc.fakeReference, nil) 357 tc.prepare(mock) 358 359 sut := sign.New(sign.Default()) 360 sut.SetImpl(mock) 361 362 obj, err := sut.VerifyImage(tc.fakeReference.image) 363 tc.assert(obj, err) 364 } 365 } 366 367 func TestVerifyFile(t *testing.T) { 368 t.Parallel() 369 370 // Create temporary directory for files. 371 tempDir, err := os.MkdirTemp("", "k8s-test-file-") 372 require.Nil(t, err) 373 defer func() { 374 require.Nil(t, os.RemoveAll(tempDir)) 375 }() 376 377 // Create temporary file for test. 378 tempFile := filepath.Join(tempDir, "test-file") 379 380 payload := []byte("honk") 381 payloadSha256 := "4de18cc93efe15c1d1cc2407cfc9f054b4d9217975538ac005dba541acee1954" 382 uuid := "uuid" 383 var logindex int64 = 1 384 uuids := []models.LogEntryAnon{ 385 { 386 LogID: &uuid, 387 LogIndex: &logindex, 388 }, 389 } 390 require.Nil(t, os.WriteFile(tempFile, payload, 0o644)) 391 392 for _, tc := range []struct { 393 path string 394 options *sign.Options 395 prepare func(*signfakes.FakeImpl) 396 assert func(*sign.SignedObject, error) 397 }{ 398 { // Success 399 path: tempFile, 400 options: sign.Default(), 401 prepare: func(mock *signfakes.FakeImpl) { 402 mock.PayloadBytesReturns(payload, nil) 403 mock.FindTlogEntryReturns(uuids, nil) 404 }, 405 assert: func(obj *sign.SignedObject, err error) { 406 require.NotNil(t, obj.File) 407 require.Equal(t, obj.File().Path(), tempFile) 408 require.Equal(t, obj.File().SHA256(), payloadSha256) 409 require.Nil(t, err) 410 }, 411 }, 412 { // File not signed 413 path: tempFile, 414 options: sign.Default(), 415 prepare: func(mock *signfakes.FakeImpl) { 416 mock.PayloadBytesReturns(nil, nil) 417 mock.FindTlogEntryReturns(nil, nil) 418 }, 419 assert: func(obj *sign.SignedObject, err error) { 420 require.Nil(t, obj) 421 require.Nil(t, err) 422 }, 423 }, 424 { // File tlog not found 425 path: tempFile, 426 options: sign.Default(), 427 prepare: func(mock *signfakes.FakeImpl) { 428 mock.PayloadBytesReturns(payload, nil) 429 mock.FindTlogEntryReturns(uuids, nil) 430 mock.VerifyFileInternalReturns(errTest) 431 }, 432 assert: func(obj *sign.SignedObject, err error) { 433 require.Nil(t, obj) 434 require.NotNil(t, err) 435 require.ErrorContains(t, err, "verify file reference") 436 }, 437 }, 438 { // File tlog error 439 path: tempFile, 440 options: sign.Default(), 441 prepare: func(mock *signfakes.FakeImpl) { 442 mock.PayloadBytesReturns(payload, nil) 443 mock.FindTlogEntryReturns(nil, errTest) 444 }, 445 assert: func(obj *sign.SignedObject, err error) { 446 require.Nil(t, obj) 447 require.NotNil(t, err) 448 require.ErrorContains(t, err, "find rekor tlog entries") 449 }, 450 }, 451 } { 452 mock := &signfakes.FakeImpl{} 453 tc.prepare(mock) 454 455 tmpDir := t.TempDir() 456 _, pubFile := generateKeyFile(t, tmpDir, nil) 457 opts := tc.options 458 opts.Verbose = true 459 opts.PublicKeyPath = pubFile 460 461 sut := sign.New(opts) 462 sut.SetImpl(mock) 463 464 obj, err := sut.VerifyFile(tc.path, false) 465 tc.assert(obj, err) 466 } 467 } 468 469 func generateKeyFile(t *testing.T, tmpDir string, pf cosign.PassFunc) (privFile, pubFile string) { 470 t.Helper() 471 472 tmpPrivFile, err := os.CreateTemp(tmpDir, "cosign_test_*.key") 473 if err != nil { 474 t.Fatalf("failed to create temp key file: %v", err) 475 } 476 defer tmpPrivFile.Close() 477 tmpPubFile, err := os.CreateTemp(tmpDir, "cosign_test_*.pub") 478 if err != nil { 479 t.Fatalf("failed to create temp pub file: %v", err) 480 } 481 defer tmpPubFile.Close() 482 483 // Generate a valid keypair. 484 keys, err := cosign.GenerateKeyPair(pf) 485 if err != nil { 486 t.Fatalf("failed to generate keypair: %v", err) 487 } 488 489 if _, err := tmpPrivFile.Write(keys.PrivateBytes); err != nil { 490 t.Fatalf("failed to write key file: %v", err) 491 } 492 if _, err := tmpPubFile.Write(keys.PublicBytes); err != nil { 493 t.Fatalf("failed to write pub file: %v", err) 494 } 495 return tmpPrivFile.Name(), tmpPubFile.Name() 496 } 497 498 func TestIsImageSigned(t *testing.T) { 499 t.Parallel() 500 501 for _, tc := range []struct { 502 prepare func(*signfakes.FakeImpl) 503 assert func(bool, error) 504 }{ 505 { // Success, signed 506 prepare: func(mock *signfakes.FakeImpl) { 507 m := &sync.Map{} 508 m.Store("", true) 509 mock.ImagesSignedReturns(m, nil) 510 }, 511 assert: func(signed bool, err error) { 512 require.True(t, signed) 513 require.Nil(t, err) 514 }, 515 }, 516 { // Success, not signed 517 prepare: func(mock *signfakes.FakeImpl) { 518 m := &sync.Map{} 519 m.Store("", false) 520 mock.ImagesSignedReturns(m, nil) 521 }, 522 assert: func(signed bool, err error) { 523 require.False(t, signed) 524 require.Nil(t, err) 525 }, 526 }, 527 { // failure ImagesSigned errors 528 prepare: func(mock *signfakes.FakeImpl) { 529 mock.ImagesSignedReturns(nil, errTest) 530 }, 531 assert: func(_ bool, err error) { 532 require.Error(t, err) 533 }, 534 }, 535 { // failure ref not part of the result 536 prepare: func(mock *signfakes.FakeImpl) { 537 m := &sync.Map{} 538 mock.ImagesSignedReturns(m, nil) 539 }, 540 assert: func(_ bool, err error) { 541 require.Error(t, err) 542 }, 543 }, 544 { // failure on interface conversion 545 prepare: func(mock *signfakes.FakeImpl) { 546 m := &sync.Map{} 547 m.Store("", 1) 548 mock.ImagesSignedReturns(m, nil) 549 }, 550 assert: func(_ bool, err error) { 551 require.Error(t, err) 552 }, 553 }, 554 } { 555 mock := &signfakes.FakeImpl{} 556 tc.prepare(mock) 557 558 sut := sign.New(sign.Default()) 559 sut.SetImpl(mock) 560 561 res, err := sut.IsImageSigned("") 562 tc.assert(res, err) 563 } 564 } 565 566 // FakeReferenceStub implements the name.Reference to we use in the testing 567 // 568 // type FakeReferenceStub interface { 569 // fmt.Stringer 570 // // Context accesses the Repository context of the reference. 571 // Context() name.Repository 572 // // Identifier accesses the type-specific portion of the reference. 573 // Identifier() string 574 // // Name is the fully-qualified reference name. 575 // Name() string 576 // // Scope is the scope needed to access this reference. 577 // Scope(string) 578 // } 579 type FakeReferenceStub struct { 580 image string 581 registry string 582 repository string 583 } 584 585 func (fr *FakeReferenceStub) Context() name.Repository { 586 reg, err := name.NewRepository(fr.repository, name.WithDefaultRegistry(fr.registry)) 587 if err != nil { 588 log.Fatal(err) 589 } 590 return reg 591 } 592 593 func (*FakeReferenceStub) Identifier() string { 594 return "" 595 } 596 597 func (*FakeReferenceStub) Scope(s string) string { 598 return s 599 } 600 601 func (fr *FakeReferenceStub) Name() string { 602 return fr.image 603 } 604 605 func (fr *FakeReferenceStub) String() string { 606 return fr.image 607 } 608 609 type testRoundTripper struct{} 610 611 func (t *testRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { 612 return nil, nil 613 } 614 615 func TestImagesSigned(t *testing.T) { 616 t.Parallel() 617 618 fakeRef := &FakeReferenceStub{ 619 image: "gcr.io/fake/honk:99.99.99", 620 registry: "gcr.io", 621 repository: "fake/honk", 622 } 623 624 for _, tc := range []struct { 625 prepare func(*signfakes.FakeImpl) 626 assert func(*sync.Map, error) 627 }{ 628 { // Success, signed 629 prepare: func(mock *signfakes.FakeImpl) { 630 mock.ParseReferenceReturns(fakeRef, nil) 631 mock.NewWithContextReturns(&testRoundTripper{}, nil) 632 }, 633 assert: func(res *sync.Map, err error) { 634 require.Nil(t, err) 635 636 signed, ok := res.Load("") 637 require.True(t, ok) 638 require.True(t, signed.(bool)) 639 }, 640 }, 641 { // Success, unsigned 642 prepare: func(mock *signfakes.FakeImpl) { 643 mock.ParseReferenceReturns(fakeRef, nil) 644 mock.NewWithContextReturns(&testRoundTripper{}, nil) 645 mock.DigestReturnsOnCall(1, "", &transport.Error{ 646 Errors: []transport.Diagnostic{ 647 {Code: transport.ManifestUnknownErrorCode}, 648 }, 649 }) 650 }, 651 assert: func(res *sync.Map, err error) { 652 require.Nil(t, err) 653 654 signed, ok := res.Load("") 655 require.True(t, ok) 656 require.False(t, signed.(bool)) 657 }, 658 }, 659 { // failure on ParseReference 660 prepare: func(mock *signfakes.FakeImpl) { 661 mock.ParseReferenceReturns(nil, errTest) 662 }, 663 assert: func(res *sync.Map, err error) { 664 require.NotNil(t, err) 665 require.Nil(t, res) 666 }, 667 }, 668 { // failure on NewWithContext 669 prepare: func(mock *signfakes.FakeImpl) { 670 mock.ParseReferenceReturns(fakeRef, nil) 671 mock.NewWithContextReturns(nil, errTest) 672 }, 673 assert: func(res *sync.Map, err error) { 674 require.NotNil(t, err) 675 require.Nil(t, res) 676 }, 677 }, 678 { // failure on first Digest 679 prepare: func(mock *signfakes.FakeImpl) { 680 mock.ParseReferenceReturns(fakeRef, nil) 681 mock.NewWithContextReturns(&testRoundTripper{}, nil) 682 mock.DigestReturns("", errTest) 683 }, 684 assert: func(res *sync.Map, err error) { 685 require.NotNil(t, err) 686 require.NotNil(t, res) // partial results are possible 687 }, 688 }, 689 { // failure on second Digest 690 prepare: func(mock *signfakes.FakeImpl) { 691 mock.ParseReferenceReturns(fakeRef, nil) 692 mock.NewWithContextReturns(&testRoundTripper{}, nil) 693 mock.DigestReturnsOnCall(1, "", errTest) 694 }, 695 assert: func(res *sync.Map, err error) { 696 require.NotNil(t, err) 697 require.NotNil(t, res) // partial results are possible 698 }, 699 }, 700 } { 701 mock := &signfakes.FakeImpl{} 702 tc.prepare(mock) 703 704 sut := sign.New(sign.Default()) 705 sut.SetImpl(mock) 706 707 res, err := sut.ImagesSigned(context.TODO(), "") 708 tc.assert(res, err) 709 } 710 }