1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package e2e
19
20 import (
21 "bytes"
22 "context"
23 "crypto"
24 "crypto/ecdsa"
25 "crypto/sha256"
26 "encoding/hex"
27 "encoding/json"
28 "errors"
29 "fmt"
30 "io/ioutil"
31 "net/http"
32 "os"
33 "path/filepath"
34 "strconv"
35 "strings"
36 "testing"
37 "time"
38
39 "golang.org/x/sync/errgroup"
40
41 "cloud.google.com/go/pubsub"
42 "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
43 "github.com/go-openapi/strfmt"
44 "github.com/go-openapi/swag"
45 "github.com/google/go-cmp/cmp"
46 "github.com/google/go-cmp/cmp/cmpopts"
47 "github.com/sigstore/rekor/pkg/client"
48 generatedClient "github.com/sigstore/rekor/pkg/generated/client"
49 "github.com/sigstore/rekor/pkg/generated/client/entries"
50 "github.com/sigstore/rekor/pkg/generated/client/pubkey"
51 "github.com/sigstore/rekor/pkg/generated/models"
52 sigx509 "github.com/sigstore/rekor/pkg/pki/x509"
53 "github.com/sigstore/rekor/pkg/sharding"
54 "github.com/sigstore/rekor/pkg/signer"
55 _ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1"
56 rekord "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
57 "github.com/sigstore/sigstore/pkg/cryptoutils"
58 "github.com/sigstore/sigstore/pkg/signature"
59 "github.com/sigstore/sigstore/pkg/signature/options"
60 )
61
62 func getUUIDFromUploadOutput(t *testing.T, out string) string {
63 t.Helper()
64
65 urlTokens := strings.Split(strings.TrimSpace(out), " ")
66 url := urlTokens[len(urlTokens)-1]
67 splitUrl := strings.Split(url, "/")
68 return splitUrl[len(splitUrl)-1]
69 }
70
71 func getLogIndexFromUploadOutput(t *testing.T, out string) int {
72 t.Helper()
73 t.Log(out)
74
75 split := strings.Split(strings.TrimSpace(out), ",")
76 ss := strings.Split(split[0], " ")
77 i, err := strconv.Atoi(ss[len(ss)-1])
78 if err != nil {
79 t.Fatal(err)
80 }
81 return i
82 }
83
84 func TestEnvVariableValidation(t *testing.T) {
85 os.Setenv("REKOR_FORMAT", "bogus")
86 defer os.Unsetenv("REKOR_FORMAT")
87
88 runCliErr(t, "loginfo")
89 }
90
91 func TestDuplicates(t *testing.T) {
92 artifactPath := filepath.Join(t.TempDir(), "artifact")
93 sigPath := filepath.Join(t.TempDir(), "signature.asc")
94
95 createdPGPSignedArtifact(t, artifactPath, sigPath)
96
97
98 pubPath := filepath.Join(t.TempDir(), "pubKey.asc")
99 if err := ioutil.WriteFile(pubPath, []byte(publicKey), 0644); err != nil {
100 t.Fatal(err)
101 }
102
103
104 out := runCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
105 outputContains(t, out, "Created entry at")
106
107
108 out = runCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
109 outputContains(t, out, "Entry already exists")
110
111
112 createdPGPSignedArtifact(t, artifactPath, sigPath)
113 out = runCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
114 outputContains(t, out, "Created entry at")
115 }
116
117 type getOut struct {
118 Attestation string
119 AttestationType string
120 Body interface{}
121 LogIndex int
122 IntegratedTime int64
123 }
124
125 func TestGetCLI(t *testing.T) {
126
127 artifactPath := filepath.Join(t.TempDir(), "artifact")
128 sigPath := filepath.Join(t.TempDir(), "signature.asc")
129
130 createdPGPSignedArtifact(t, artifactPath, sigPath)
131
132
133 pubPath := filepath.Join(t.TempDir(), "pubKey.asc")
134 if err := ioutil.WriteFile(pubPath, []byte(publicKey), 0644); err != nil {
135 t.Fatal(err)
136 }
137 out := runCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
138 outputContains(t, out, "Created entry at")
139
140 uuid, err := sharding.GetUUIDFromIDString(getUUIDFromUploadOutput(t, out))
141 if err != nil {
142 t.Error(err)
143 }
144
145
146 runCli(t, "get", "--log-index", "0")
147
148 out = runCli(t, "get", "--format=json", "--uuid", uuid)
149
150
151 g := getOut{}
152 if err := json.Unmarshal([]byte(out), &g); err != nil {
153 t.Error(err)
154 }
155
156 if g.IntegratedTime == 0 {
157 t.Errorf("Expected IntegratedTime to be set. Got %s", out)
158 }
159
160 runCli(t, "get", "--format=json", "--log-index", strconv.Itoa(g.LogIndex))
161
162
163 out = runCli(t, "search", "--artifact", artifactPath)
164 outputContains(t, out, uuid)
165
166 out = runCli(t, "search", "--public-key", pubPath)
167 outputContains(t, out, uuid)
168
169 artifactBytes, err := ioutil.ReadFile(artifactPath)
170 if err != nil {
171 t.Error(err)
172 }
173 sha := sha256.Sum256(artifactBytes)
174
175 out = runCli(t, "search", "--sha", fmt.Sprintf("sha256:%s", hex.EncodeToString(sha[:])))
176 outputContains(t, out, uuid)
177
178
179 tid := getTreeID(t)
180 entryID, err := sharding.CreateEntryIDFromParts(fmt.Sprintf("%x", tid), uuid)
181 if err != nil {
182 t.Error(err)
183 }
184 runCli(t, "get", "--format=json", "--uuid", entryID.ReturnEntryIDString())
185 }
186
187 func publicKeyFromRekorClient(ctx context.Context, c *generatedClient.Rekor) (*ecdsa.PublicKey, error) {
188 resp, err := c.Pubkey.GetPublicKey(&pubkey.GetPublicKeyParams{Context: ctx})
189 if err != nil {
190 return nil, err
191 }
192
193
194 pubKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(resp.GetPayload()))
195 if err != nil {
196 return nil, err
197 }
198 ed, ok := pubKey.(*ecdsa.PublicKey)
199 if !ok {
200 return nil, errors.New("public key retrieved from Rekor is not an ECDSA key")
201 }
202 return ed, nil
203 }
204
205 func TestSignedEntryTimestamp(t *testing.T) {
206
207 ctx := context.Background()
208 payload := []byte("payload")
209 s, err := signer.NewMemory()
210 if err != nil {
211 t.Fatal(err)
212 }
213 sig, err := s.SignMessage(bytes.NewReader(payload), options.WithContext(ctx))
214 if err != nil {
215 t.Fatal(err)
216 }
217 pubkey, err := s.PublicKey(options.WithContext(ctx))
218 if err != nil {
219 t.Fatal(err)
220 }
221 pemBytes, err := cryptoutils.MarshalPublicKeyToPEM(pubkey)
222 if err != nil {
223 t.Fatal(err)
224 }
225
226
227 rekorClient, err := client.GetRekorClient(rekorServer())
228 if err != nil {
229 t.Fatal(err)
230 }
231
232 re := rekord.V001Entry{
233 RekordObj: models.RekordV001Schema{
234 Data: &models.RekordV001SchemaData{
235 Content: strfmt.Base64(payload),
236 },
237 Signature: &models.RekordV001SchemaSignature{
238 Content: (*strfmt.Base64)(&sig),
239 Format: swag.String(models.RekordV001SchemaSignatureFormatX509),
240 PublicKey: &models.RekordV001SchemaSignaturePublicKey{
241 Content: (*strfmt.Base64)(&pemBytes),
242 },
243 },
244 },
245 }
246
247 returnVal := models.Rekord{
248 APIVersion: swag.String(re.APIVersion()),
249 Spec: re.RekordObj,
250 }
251 params := entries.NewCreateLogEntryParams()
252 params.SetProposedEntry(&returnVal)
253 resp, err := rekorClient.Entries.CreateLogEntry(params)
254 if err != nil {
255 t.Fatal(err)
256 }
257 logEntry := extractLogEntry(t, resp.GetPayload())
258
259
260 timestampSig := logEntry.Verification.SignedEntryTimestamp
261 logEntry.Verification = nil
262 payload, err = logEntry.MarshalBinary()
263 if err != nil {
264 t.Fatal(err)
265 }
266 canonicalized, err := jsoncanonicalizer.Transform(payload)
267 if err != nil {
268 t.Fatal(err)
269 }
270
271 rekorPubKey, err := publicKeyFromRekorClient(ctx, rekorClient)
272 if err != nil {
273 t.Fatal(err)
274 }
275
276 verifier, err := signature.LoadVerifier(rekorPubKey, crypto.SHA256)
277 if err != nil {
278 t.Fatal(err)
279 }
280 if err := verifier.VerifySignature(bytes.NewReader(timestampSig), bytes.NewReader(canonicalized), options.WithContext(ctx)); err != nil {
281 t.Fatal("unable to verify")
282 }
283 }
284
285 func TestEntryUpload(t *testing.T) {
286 artifactPath := filepath.Join(t.TempDir(), "artifact")
287 sigPath := filepath.Join(t.TempDir(), "signature.asc")
288
289
290 createdPGPSignedArtifact(t, artifactPath, sigPath)
291 payload, err := ioutil.ReadFile(artifactPath)
292 if err != nil {
293 t.Fatal(err)
294 }
295 sig, err := ioutil.ReadFile(sigPath)
296 if err != nil {
297 t.Fatal(err)
298 }
299
300 entryPath := filepath.Join(t.TempDir(), "entry.json")
301 pubKeyBytes := []byte(publicKey)
302
303 re := rekord.V001Entry{
304 RekordObj: models.RekordV001Schema{
305 Data: &models.RekordV001SchemaData{
306 Content: strfmt.Base64(payload),
307 },
308 Signature: &models.RekordV001SchemaSignature{
309 Content: (*strfmt.Base64)(&sig),
310 Format: swag.String(models.RekordV001SchemaSignatureFormatPgp),
311 PublicKey: &models.RekordV001SchemaSignaturePublicKey{
312 Content: (*strfmt.Base64)(&pubKeyBytes),
313 },
314 },
315 },
316 }
317
318 returnVal := models.Rekord{
319 APIVersion: swag.String(re.APIVersion()),
320 Spec: re.RekordObj,
321 }
322 entryBytes, err := json.Marshal(returnVal)
323 if err != nil {
324 t.Fatal(err)
325 }
326 if err := ioutil.WriteFile(entryPath, entryBytes, 0644); err != nil {
327 t.Fatal(err)
328 }
329
330
331
332 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
333 defer cancel()
334 psc, err := pubsub.NewClient(ctx, "test-project")
335 if err != nil {
336 t.Fatalf("Create pubsub client: %v", err)
337 }
338 topic, err := psc.CreateTopic(ctx, "new-entry")
339 if err != nil {
340
341
342
343 if errors.Is(err, os.ErrDeadlineExceeded) {
344 t.Fatalf("Create pubsub topic: %v", err)
345 }
346 topic = psc.Topic("new-entry")
347 }
348 filters := []string{
349 `attributes:rekor_entry_kind`,
350 `attributes.rekor_signing_subjects = "test@rekor.dev"`,
351 `attributes.datacontenttype = "application/json"`,
352 }
353 cfg := pubsub.SubscriptionConfig{
354 Topic: topic,
355 Filter: strings.Join(filters, " AND "),
356 }
357 sub, err := psc.CreateSubscription(ctx, "new-entry-sub", cfg)
358 if err != nil {
359 if errors.Is(err, os.ErrDeadlineExceeded) {
360 t.Fatalf("Create pubsub subscription: %v", err)
361 }
362 sub = psc.Subscription("new-entry-sub")
363 }
364 ch := make(chan []byte, 1)
365 go func() {
366 if err := sub.Receive(ctx, func(_ context.Context, m *pubsub.Message) {
367 ch <- m.Data
368 }); err != nil {
369 t.Errorf("Receive pubusub msg: %v", err)
370 }
371 }()
372
373
374 out := runCli(t, "upload", "--entry", entryPath)
375 outputContains(t, out, "Created entry at")
376
377
378 select {
379 case msg := <-ch:
380 t.Logf("Got pubsub message!\n%s", string(msg))
381 case <-ctx.Done():
382 t.Errorf("Did not receive pubsub message: %v", ctx.Err())
383 }
384 }
385
386
387
388
389
390 func TestInclusionProofRace(t *testing.T) {
391
392 artifactPath := filepath.Join(t.TempDir(), "artifact")
393 sigPath := filepath.Join(t.TempDir(), "signature.asc")
394
395 sigx509.CreatedX509SignedArtifact(t, artifactPath, sigPath)
396 dataBytes, _ := ioutil.ReadFile(artifactPath)
397 h := sha256.Sum256(dataBytes)
398 dataSHA := hex.EncodeToString(h[:])
399
400
401 pubPath := filepath.Join(t.TempDir(), "pubKey.asc")
402 if err := ioutil.WriteFile(pubPath, []byte(sigx509.RSACert), 0644); err != nil {
403 t.Fatal(err)
404 }
405
406
407 runCli(t, "upload", "--type=hashedrekord", "--pki-format=x509", "--artifact-hash", dataSHA, "--signature", sigPath, "--public-key", pubPath)
408
409
410 uploadRoutine := func(pubPath string) error {
411
412 artifactPath := filepath.Join(t.TempDir(), "artifact")
413 sigPath := filepath.Join(t.TempDir(), "signature.asc")
414
415 sigx509.CreatedX509SignedArtifact(t, artifactPath, sigPath)
416 dataBytes, _ := ioutil.ReadFile(artifactPath)
417 h := sha256.Sum256(dataBytes)
418 dataSHA := hex.EncodeToString(h[:])
419
420
421 out := runCli(t, "upload", "--type=hashedrekord", "--pki-format=x509", "--artifact-hash", dataSHA, "--signature", sigPath, "--public-key", pubPath)
422 outputContains(t, out, "Created entry at")
423
424 return nil
425 }
426
427
428 verifyRoutine := func(dataSHA, sigPath, pubPath string) error {
429 out := runCli(t, "verify", "--type=hashedrekord", "--pki-format=x509", "--artifact-hash", dataSHA, "--signature", sigPath, "--public-key", pubPath)
430
431 if strings.Contains(out, "calculated root") || strings.Contains(out, "wrong") {
432 return errors.New(out)
433 }
434
435 return nil
436 }
437
438 var g errgroup.Group
439 for i := 0; i < 50; i++ {
440 g.Go(func() error { return uploadRoutine(pubPath) })
441 g.Go(func() error { return verifyRoutine(dataSHA, sigPath, pubPath) })
442 }
443
444 if err := g.Wait(); err != nil {
445 t.Fatal(err)
446 }
447 }
448
449
450 func TestIssue1308(t *testing.T) {
451
452 if getTotalTreeSize(t) == 0 {
453 TestSearchQueryNonExistentEntry(t)
454 } else {
455 t.Skip("skipping because log is not empty")
456 }
457 }
458
459 func TestSearchQueryNonExistentEntry(t *testing.T) {
460
461 wd, err := os.Getwd()
462 if err != nil {
463 t.Fatal(err)
464 }
465 b, err := ioutil.ReadFile(filepath.Join(wd, "canonical_rekor.json"))
466 if err != nil {
467 t.Fatal(err)
468 }
469 body := fmt.Sprintf("{\"entries\":[%s]}", b)
470 resp, err := http.Post(fmt.Sprintf("%s/api/v1/log/entries/retrieve", rekorServer()),
471 "application/json",
472 bytes.NewBuffer([]byte(body)))
473 if err != nil {
474 t.Fatal(err)
475 }
476 c, _ := ioutil.ReadAll(resp.Body)
477 if resp.StatusCode != 200 {
478 t.Fatalf("expected status 200, got %d instead: %v", resp.StatusCode, string(c))
479 }
480 if strings.TrimSpace(string(c)) != "[]" {
481 t.Fatalf("expected empty JSON array as response, got %s instead", string(c))
482 }
483 }
484
485 func getTreeID(t *testing.T) int64 {
486 out := runCli(t, "loginfo")
487 tidStr := strings.TrimSpace(strings.Split(out, "TreeID: ")[1])
488 tid, err := strconv.ParseInt(tidStr, 10, 64)
489 if err != nil {
490 t.Errorf(err.Error())
491 }
492 t.Log("Tree ID:", tid)
493 return tid
494 }
495
496 func getTotalTreeSize(t *testing.T) int64 {
497 out := runCli(t, "loginfo")
498 sizeStr := strings.Fields(strings.Split(out, "Total Tree Size: ")[1])[0]
499 size, err := strconv.ParseInt(sizeStr, 10, 64)
500 if err != nil {
501 t.Errorf(err.Error())
502 }
503 t.Log("Total Tree Size:", size)
504 return size
505 }
506
507
508
509 func TestSearchValidateTreeID(t *testing.T) {
510
511 artifactPath := filepath.Join(t.TempDir(), "artifact")
512 sigPath := filepath.Join(t.TempDir(), "signature.asc")
513
514 createdPGPSignedArtifact(t, artifactPath, sigPath)
515
516
517 pubPath := filepath.Join(t.TempDir(), "pubKey.asc")
518 if err := ioutil.WriteFile(pubPath, []byte(publicKey), 0644); err != nil {
519 t.Fatal(err)
520 }
521 out := runCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", pubPath)
522 outputContains(t, out, "Created entry at")
523
524 uuid, err := sharding.GetUUIDFromIDString(getUUIDFromUploadOutput(t, out))
525 if err != nil {
526 t.Error(err)
527 }
528
529 tid := getTreeID(t)
530 entryID, err := sharding.CreateEntryIDFromParts(fmt.Sprintf("%x", tid), uuid)
531 if err != nil {
532 t.Fatal(err)
533 }
534 body := "{\"entryUUIDs\":[\"%s\"]}"
535 resp, err := http.Post(fmt.Sprintf("%s/api/v1/log/entries/retrieve", rekorServer()), "application/json", bytes.NewBuffer([]byte(fmt.Sprintf(body, entryID.ReturnEntryIDString()))))
536 if err != nil {
537 t.Fatal(err)
538 }
539 if resp.StatusCode != 200 {
540 t.Fatalf("expected 200 status code but got %d", resp.StatusCode)
541 }
542
543
544 fakeTID := tid + 1
545 entryID, err = sharding.CreateEntryIDFromParts(fmt.Sprintf("%x", fakeTID), uuid)
546 if err != nil {
547 t.Fatal(err)
548 }
549
550 resp, err = http.Post(fmt.Sprintf("%s/api/v1/log/entries/retrieve", rekorServer()), "application/json", bytes.NewBuffer([]byte(fmt.Sprintf(body, entryID.ReturnEntryIDString()))))
551 if err != nil {
552 t.Fatal(err)
553 }
554
555 c, _ := ioutil.ReadAll(resp.Body)
556 if resp.StatusCode != 200 {
557 t.Fatalf("expected status 200, got %d instead", resp.StatusCode)
558 }
559 if strings.TrimSpace(string(c)) != "[]" {
560 t.Fatalf("expected empty JSON array as response, got %s instead", string(c))
561 }
562 }
563
564
565 func TestSearchLogQuerySingleShard(t *testing.T) {
566
567
568 pubPath := filepath.Join(t.TempDir(), "logQuery_pubKey.asc")
569 pubKeyBytes := []byte(publicKey)
570 if err := ioutil.WriteFile(pubPath, pubKeyBytes, 0644); err != nil {
571 t.Fatal(err)
572 }
573
574
575 firstArtifactPath := filepath.Join(t.TempDir(), "artifact1")
576 firstSigPath := filepath.Join(t.TempDir(), "signature1.asc")
577 createdPGPSignedArtifact(t, firstArtifactPath, firstSigPath)
578 firstArtifactBytes, _ := ioutil.ReadFile(firstArtifactPath)
579 firstSigBytes, _ := ioutil.ReadFile(firstSigPath)
580
581 firstRekord := rekord.V001Entry{
582 RekordObj: models.RekordV001Schema{
583 Data: &models.RekordV001SchemaData{
584 Content: strfmt.Base64(firstArtifactBytes),
585 },
586 Signature: &models.RekordV001SchemaSignature{
587 Content: (*strfmt.Base64)(&firstSigBytes),
588 Format: swag.String(models.RekordV001SchemaSignatureFormatPgp),
589 PublicKey: &models.RekordV001SchemaSignaturePublicKey{
590 Content: (*strfmt.Base64)(&pubKeyBytes),
591 },
592 },
593 },
594 }
595 firstEntry := &models.Rekord{
596 APIVersion: swag.String(firstRekord.APIVersion()),
597 Spec: firstRekord.RekordObj,
598 }
599
600 secondArtifactPath := filepath.Join(t.TempDir(), "artifact2")
601 secondSigPath := filepath.Join(t.TempDir(), "signature2.asc")
602 createdPGPSignedArtifact(t, secondArtifactPath, secondSigPath)
603 secondArtifactBytes, _ := ioutil.ReadFile(secondArtifactPath)
604 secondSigBytes, _ := ioutil.ReadFile(secondSigPath)
605
606 secondRekord := rekord.V001Entry{
607 RekordObj: models.RekordV001Schema{
608 Data: &models.RekordV001SchemaData{
609 Content: strfmt.Base64(secondArtifactBytes),
610 },
611 Signature: &models.RekordV001SchemaSignature{
612 Content: (*strfmt.Base64)(&secondSigBytes),
613 Format: swag.String(models.RekordV001SchemaSignatureFormatPgp),
614 PublicKey: &models.RekordV001SchemaSignaturePublicKey{
615 Content: (*strfmt.Base64)(&pubKeyBytes),
616 },
617 },
618 },
619 }
620 secondEntry := &models.Rekord{
621 APIVersion: swag.String(secondRekord.APIVersion()),
622 Spec: secondRekord.RekordObj,
623 }
624
625
626 firstOut := runCli(t, "upload", "--artifact", firstArtifactPath, "--signature", firstSigPath, "--public-key", pubPath)
627 secondOut := runCli(t, "upload", "--artifact", secondArtifactPath, "--signature", secondSigPath, "--public-key", pubPath)
628
629 firstEntryID := getUUIDFromUploadOutput(t, firstOut)
630 firstUUID, _ := sharding.GetUUIDFromIDString(firstEntryID)
631 firstIndex := int64(getLogIndexFromUploadOutput(t, firstOut))
632 secondEntryID := getUUIDFromUploadOutput(t, secondOut)
633 secondUUID, _ := sharding.GetUUIDFromIDString(secondEntryID)
634 secondIndex := int64(getLogIndexFromUploadOutput(t, secondOut))
635
636
637 invalidEntryID := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeeefff"
638 invalidIndex := int64(-1)
639 invalidEntry := &models.Rekord{
640 APIVersion: swag.String(secondRekord.APIVersion()),
641 }
642
643 nonexistentArtifactPath := filepath.Join(t.TempDir(), "artifact3")
644 nonexistentSigPath := filepath.Join(t.TempDir(), "signature3.asc")
645 createdPGPSignedArtifact(t, nonexistentArtifactPath, nonexistentSigPath)
646 nonexistentArtifactBytes, _ := ioutil.ReadFile(nonexistentArtifactPath)
647 nonexistentSigBytes, _ := ioutil.ReadFile(nonexistentSigPath)
648
649 nonexistentEntryID := "0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeeefff"
650 nonexistentUUID := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeeefff"
651 nonexistentIndex := int64(999999999)
652 nonexistentRekord := rekord.V001Entry{
653 RekordObj: models.RekordV001Schema{
654 Data: &models.RekordV001SchemaData{
655 Content: strfmt.Base64(nonexistentArtifactBytes),
656 },
657 Signature: &models.RekordV001SchemaSignature{
658 Content: (*strfmt.Base64)(&nonexistentSigBytes),
659 Format: swag.String(models.RekordV001SchemaSignatureFormatPgp),
660 PublicKey: &models.RekordV001SchemaSignaturePublicKey{
661 Content: (*strfmt.Base64)(&pubKeyBytes),
662 },
663 },
664 },
665 }
666 nonexistentEntry := &models.Rekord{
667 APIVersion: swag.String("0.0.1"),
668 Spec: nonexistentRekord.RekordObj,
669 }
670
671 type testCase struct {
672 name string
673 expectSuccess bool
674 expectedErrorResponseCode int64
675 expectedEntryIDs []string
676 entryUUIDs []string
677 logIndexes []*int64
678 entries []models.ProposedEntry
679 }
680
681 testCases := []testCase{
682 {
683 name: "empty entryUUIDs",
684 expectSuccess: true,
685 expectedEntryIDs: []string{},
686 entryUUIDs: []string{},
687 },
688 {
689 name: "first in log (using entryUUIDs)",
690 expectSuccess: true,
691 expectedEntryIDs: []string{firstEntryID},
692 entryUUIDs: []string{firstEntryID},
693 },
694 {
695 name: "first in log (using UUID in entryUUIDs)",
696 expectSuccess: true,
697 expectedEntryIDs: []string{firstEntryID},
698 entryUUIDs: []string{firstUUID},
699 },
700 {
701 name: "second in log (using entryUUIDs)",
702 expectSuccess: true,
703 expectedEntryIDs: []string{secondEntryID},
704 entryUUIDs: []string{secondEntryID},
705 },
706 {
707 name: "invalid entryID (using entryUUIDs)",
708 expectSuccess: false,
709 expectedErrorResponseCode: http.StatusBadRequest,
710 entryUUIDs: []string{invalidEntryID},
711 },
712 {
713 name: "valid entryID not in log (using entryUUIDs)",
714 expectSuccess: true,
715 expectedEntryIDs: []string{},
716 entryUUIDs: []string{nonexistentEntryID},
717 },
718 {
719 name: "valid UUID not in log (using entryUUIDs)",
720 expectSuccess: true,
721 expectedEntryIDs: []string{},
722 entryUUIDs: []string{nonexistentUUID},
723 },
724 {
725 name: "both valid entries in log (using entryUUIDs)",
726 expectSuccess: true,
727 expectedEntryIDs: []string{firstEntryID, secondEntryID},
728 entryUUIDs: []string{firstEntryID, secondEntryID},
729 },
730 {
731 name: "both valid entries in log (one with UUID, other with entryID) (using entryUUIDs)",
732 expectSuccess: true,
733 expectedEntryIDs: []string{firstEntryID, secondEntryID},
734 entryUUIDs: []string{firstEntryID, secondUUID},
735 },
736 {
737 name: "one valid entry in log, one malformed (using entryUUIDs)",
738 expectSuccess: false,
739 expectedErrorResponseCode: http.StatusBadRequest,
740 entryUUIDs: []string{firstEntryID, invalidEntryID},
741 },
742 {
743 name: "one existing, one valid entryID but not in log (using entryUUIDs)",
744 expectSuccess: true,
745 expectedEntryIDs: []string{firstEntryID},
746 entryUUIDs: []string{firstEntryID, nonexistentEntryID},
747 },
748 {
749 name: "two existing, one valid entryID but not in log (using entryUUIDs)",
750 expectSuccess: true,
751 expectedEntryIDs: []string{firstEntryID, secondEntryID},
752 entryUUIDs: []string{firstEntryID, secondEntryID, nonexistentEntryID},
753 },
754 {
755 name: "two existing, one valid entryID but not in log (different ordering 1) (using entryUUIDs)",
756 expectSuccess: true,
757 expectedEntryIDs: []string{firstEntryID, secondEntryID},
758 entryUUIDs: []string{firstEntryID, nonexistentEntryID, secondEntryID},
759 },
760 {
761 name: "two existing, one valid entryID but not in log (different ordering 2) (using entryUUIDs)",
762 expectSuccess: true,
763 expectedEntryIDs: []string{firstEntryID, secondEntryID},
764 entryUUIDs: []string{nonexistentEntryID, firstEntryID, secondEntryID},
765 },
766 {
767 name: "two existing, one valid entryID but not in log (different ordering 3) (using entryUUIDs)",
768 expectSuccess: true,
769 expectedEntryIDs: []string{firstEntryID, secondEntryID},
770 entryUUIDs: []string{nonexistentUUID, firstEntryID, secondEntryID},
771 },
772 {
773 name: "two existing, one valid entryID but not in log (different ordering 4) (using entryUUIDs)",
774 expectSuccess: true,
775 expectedEntryIDs: []string{firstEntryID, secondEntryID},
776 entryUUIDs: []string{nonexistentEntryID, firstUUID, secondEntryID},
777 },
778 {
779 name: "two existing, one valid entryID but not in log (different ordering 5) (using entryUUIDs)",
780 expectSuccess: true,
781 expectedEntryIDs: []string{firstEntryID, secondEntryID},
782 entryUUIDs: []string{nonexistentEntryID, firstEntryID, secondUUID},
783 },
784 {
785 name: "two existing, one valid entryID but not in log (different ordering 6) (using entryUUIDs)",
786 expectSuccess: true,
787 expectedEntryIDs: []string{firstEntryID, secondEntryID},
788 entryUUIDs: []string{nonexistentUUID, firstEntryID, secondUUID},
789 },
790 {
791 name: "two existing, one valid entryID but not in log (different ordering 7) (using entryUUIDs)",
792 expectSuccess: true,
793 expectedEntryIDs: []string{firstEntryID, secondEntryID},
794 entryUUIDs: []string{nonexistentEntryID, firstUUID, secondUUID},
795 },
796 {
797 name: "request more than 10 entries (using entryUUIDs)",
798 expectSuccess: false,
799 expectedErrorResponseCode: http.StatusUnprocessableEntity,
800 entryUUIDs: []string{firstEntryID, firstEntryID, firstEntryID, firstEntryID, firstEntryID, firstEntryID, firstEntryID, firstEntryID, firstEntryID, firstEntryID, firstEntryID},
801 },
802 {
803 name: "empty logIndexes",
804 expectSuccess: true,
805 expectedEntryIDs: []string{},
806 logIndexes: []*int64{},
807 },
808 {
809 name: "first in log (using logIndexes)",
810 expectSuccess: true,
811 expectedEntryIDs: []string{firstEntryID},
812 logIndexes: []*int64{&firstIndex},
813 },
814 {
815 name: "second in log (using logIndexes)",
816 expectSuccess: true,
817 expectedEntryIDs: []string{secondEntryID},
818 logIndexes: []*int64{&secondIndex},
819 },
820 {
821 name: "invalid logIndex (using logIndexes)",
822 expectSuccess: false,
823 expectedErrorResponseCode: http.StatusUnprocessableEntity,
824 logIndexes: []*int64{&invalidIndex},
825 },
826 {
827 name: "valid index not in log (using logIndexes)",
828 expectSuccess: true,
829 expectedEntryIDs: []string{},
830 logIndexes: []*int64{&nonexistentIndex},
831 },
832 {
833 name: "both valid entries in log (using logIndexes)",
834 expectSuccess: true,
835 expectedEntryIDs: []string{firstEntryID, secondEntryID},
836 logIndexes: []*int64{&firstIndex, &secondIndex},
837 },
838 {
839 name: "one valid entry in log, one malformed (using logIndexes)",
840 expectSuccess: false,
841 expectedErrorResponseCode: http.StatusUnprocessableEntity,
842 logIndexes: []*int64{&firstIndex, &invalidIndex},
843 },
844 {
845 name: "one existing, one valid Index but not in log (using logIndexes)",
846 expectSuccess: true,
847 expectedEntryIDs: []string{firstEntryID},
848 logIndexes: []*int64{&firstIndex, &nonexistentIndex},
849 },
850 {
851 name: "two existing, one valid Index but not in log (using logIndexes)",
852 expectSuccess: true,
853 expectedEntryIDs: []string{firstEntryID, secondEntryID},
854 logIndexes: []*int64{&firstIndex, &secondIndex, &nonexistentIndex},
855 },
856 {
857 name: "two existing, one valid Index but not in log (different ordering 1) (using logIndexes)",
858 expectSuccess: true,
859 expectedEntryIDs: []string{firstEntryID, secondEntryID},
860 logIndexes: []*int64{&firstIndex, &nonexistentIndex, &secondIndex},
861 },
862 {
863 name: "two existing, one valid Index but not in log (different ordering 2) (using logIndexes)",
864 expectSuccess: true,
865 expectedEntryIDs: []string{firstEntryID, secondEntryID},
866 logIndexes: []*int64{&nonexistentIndex, &firstIndex, &secondIndex},
867 },
868 {
869 name: "request more than 10 entries (using logIndexes)",
870 expectSuccess: false,
871 expectedErrorResponseCode: http.StatusUnprocessableEntity,
872 logIndexes: []*int64{&firstIndex, &firstIndex, &firstIndex, &firstIndex, &firstIndex, &firstIndex, &firstIndex, &firstIndex, &firstIndex, &firstIndex, &firstIndex},
873 },
874 {
875 name: "empty entries",
876 expectSuccess: true,
877 expectedEntryIDs: []string{},
878 entries: []models.ProposedEntry{},
879 },
880 {
881 name: "first in log (using entries)",
882 expectSuccess: true,
883 expectedEntryIDs: []string{firstEntryID},
884 entries: []models.ProposedEntry{firstEntry},
885 },
886 {
887 name: "second in log (using entries)",
888 expectSuccess: true,
889 expectedEntryIDs: []string{secondEntryID},
890 entries: []models.ProposedEntry{secondEntry},
891 },
892 {
893 name: "invalid entry (using entries)",
894 expectSuccess: false,
895 expectedErrorResponseCode: http.StatusUnprocessableEntity,
896 entries: []models.ProposedEntry{invalidEntry},
897 },
898 {
899 name: "valid entry not in log (using entries)",
900 expectSuccess: true,
901 expectedEntryIDs: []string{},
902 entries: []models.ProposedEntry{nonexistentEntry},
903 },
904 {
905 name: "both valid entries in log (using entries)",
906 expectSuccess: true,
907 expectedEntryIDs: []string{firstEntryID, secondEntryID},
908 entries: []models.ProposedEntry{firstEntry, secondEntry},
909 },
910 {
911 name: "one valid entry in log, one malformed (using entries)",
912 expectSuccess: false,
913 expectedErrorResponseCode: http.StatusUnprocessableEntity,
914 entries: []models.ProposedEntry{firstEntry, invalidEntry},
915 },
916 {
917 name: "one existing, one valid Index but not in log (using entries)",
918 expectSuccess: true,
919 expectedEntryIDs: []string{firstEntryID},
920 entries: []models.ProposedEntry{firstEntry, nonexistentEntry},
921 },
922 {
923 name: "two existing, one valid Index but not in log (using entries)",
924 expectSuccess: true,
925 expectedEntryIDs: []string{firstEntryID, secondEntryID},
926 entries: []models.ProposedEntry{firstEntry, secondEntry, nonexistentEntry},
927 },
928 {
929 name: "two existing, one valid Index but not in log (different ordering 1) (using entries)",
930 expectSuccess: true,
931 expectedEntryIDs: []string{firstEntryID, secondEntryID},
932 entries: []models.ProposedEntry{firstEntry, nonexistentEntry, secondEntry},
933 },
934 {
935 name: "two existing, one valid Index but not in log (different ordering 2) (using entries)",
936 expectSuccess: true,
937 expectedEntryIDs: []string{firstEntryID, secondEntryID},
938 entries: []models.ProposedEntry{nonexistentEntry, firstEntry, secondEntry},
939 },
940 {
941 name: "request more than 10 entries (using entries)",
942 expectSuccess: false,
943 expectedErrorResponseCode: http.StatusUnprocessableEntity,
944 entries: []models.ProposedEntry{firstEntry, firstEntry, firstEntry, firstEntry, firstEntry, firstEntry, firstEntry, firstEntry, firstEntry, firstEntry, firstEntry},
945 },
946 {
947 name: "request more than 10 entries (using mixture)",
948 expectSuccess: false,
949 expectedErrorResponseCode: http.StatusUnprocessableEntity,
950 entryUUIDs: []string{firstEntryID, firstEntryID, firstEntryID, firstEntryID},
951 logIndexes: []*int64{&firstIndex, &firstIndex, &firstIndex},
952 entries: []models.ProposedEntry{firstEntry, firstEntry, firstEntry, firstEntry},
953 },
954 {
955 name: "request valid and invalid (using mixture)",
956 expectSuccess: false,
957 expectedErrorResponseCode: http.StatusUnprocessableEntity,
958 entryUUIDs: []string{firstEntryID, firstEntryID, firstEntryID, firstEntryID},
959 logIndexes: []*int64{&invalidIndex, &invalidIndex, &invalidIndex},
960 entries: []models.ProposedEntry{firstEntry, firstEntry, firstEntry},
961 },
962 {
963 name: "request valid and nonexistent (using mixture)",
964 expectSuccess: true,
965 expectedEntryIDs: []string{firstEntryID, secondEntryID, firstEntryID, secondEntryID, firstEntryID, secondEntryID},
966 entryUUIDs: []string{firstEntryID, secondEntryID, nonexistentEntryID},
967 logIndexes: []*int64{&firstIndex, &secondIndex, &nonexistentIndex},
968 entries: []models.ProposedEntry{firstEntry, secondEntry, nonexistentEntry},
969 },
970 }
971
972 for _, test := range testCases {
973 rekorClient, err := client.GetRekorClient(rekorServer(), client.WithRetryCount(0))
974 if err != nil {
975 t.Fatal(err)
976 }
977 t.Run(test.name, func(t *testing.T) {
978 params := entries.NewSearchLogQueryParams()
979 entry := &models.SearchLogQuery{}
980 if len(test.entryUUIDs) > 0 {
981 t.Log("trying with entryUUIDs: ", test.entryUUIDs)
982 entry.EntryUUIDs = test.entryUUIDs
983 }
984 if len(test.logIndexes) > 0 {
985 entry.LogIndexes = test.logIndexes
986 }
987 if len(test.entries) > 0 {
988 entry.SetEntries(test.entries)
989 }
990 params.SetEntry(entry)
991
992 resp, err := rekorClient.Entries.SearchLogQuery(params)
993 if err != nil {
994 if !test.expectSuccess {
995 if _, ok := err.(*entries.SearchLogQueryBadRequest); ok {
996 if test.expectedErrorResponseCode != http.StatusBadRequest {
997 t.Fatalf("unexpected error code received: expected %d, got %d: %v", test.expectedErrorResponseCode, http.StatusBadRequest, err)
998 }
999 } else if _, ok := err.(*entries.SearchLogQueryUnprocessableEntity); ok {
1000 if test.expectedErrorResponseCode != http.StatusUnprocessableEntity {
1001 t.Fatalf("unexpected error code received: expected %d, got %d: %v", test.expectedErrorResponseCode, http.StatusUnprocessableEntity, err)
1002 }
1003 } else if e, ok := err.(*entries.SearchLogQueryDefault); ok {
1004 t.Fatalf("unexpected error: %v", e)
1005 }
1006 } else {
1007 t.Fatalf("unexpected error: %v", err)
1008 }
1009 } else {
1010 if len(resp.Payload) != len(test.expectedEntryIDs) {
1011 t.Fatalf("unexpected number of responses received: expected %d, got %d", len(test.expectedEntryIDs), len(resp.Payload))
1012 }
1013
1014 returnedEntryIDs := []string{}
1015 for _, entry := range resp.Payload {
1016
1017 for entryID := range entry {
1018 t.Log("received entry: ", entryID)
1019 returnedEntryIDs = append(returnedEntryIDs, entryID)
1020 }
1021 }
1022
1023 if out := cmp.Diff(returnedEntryIDs, test.expectedEntryIDs, cmpopts.SortSlices(func(a, b string) bool { return a < b })); out != "" {
1024 t.Fatalf("unexpected responses: %v", out)
1025 }
1026 }
1027 })
1028 }
1029 }
1030
View as plain text