diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index 68cac7ff6c1..ff81be227bd 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -34,6 +34,8 @@ import ( "golang.org/x/sync/errgroup" ) +const maxAllowedSigsOrAtts = 100 + type SignedPayload struct { Base64Signature string Payload []byte @@ -84,6 +86,9 @@ func FetchSignaturesForReference(_ context.Context, ref name.Reference, opts ... if len(l) == 0 { return nil, fmt.Errorf("no signatures associated with %s", ref) } + if len(l) > maxAllowedSigsOrAtts { + return nil, fmt.Errorf("maximum number of signatures on an image is %d, found %d", maxAllowedSigsOrAtts, len(l)) + } signatures := make([]SignedPayload, len(l)) var g errgroup.Group @@ -145,6 +150,10 @@ func FetchAttestations(se oci.SignedEntity, predicateType string) ([]Attestation if len(l) == 0 { return nil, errors.New("found no attestations") } + if len(l) > maxAllowedSigsOrAtts { + errMsg := fmt.Sprintf("maximum number of attestations on an image is %d, found %d", maxAllowedSigsOrAtts, len(l)) + return nil, errors.New(errMsg) + } attestations := make([]AttestationPayload, 0, len(l)) var attMu sync.Mutex diff --git a/test/e2e_test.go b/test/e2e_test.go index bef1e0db965..ff81877f057 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -661,6 +661,88 @@ func TestAttestationReplaceCreate(t *testing.T) { } } +func TestExcessiveAttestations(t *testing.T) { + repo, stop := reg(t) + defer stop() + td := t.TempDir() + + imgName := path.Join(repo, "cosign-attest-download-e2e") + + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + _, privKeyPath, _ := keypair(t, td) + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + + ctx := context.Background() + + slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") + if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { + t.Fatal(err) + } + + vulnAttestation := ` + { + "invocation": { + "parameters": null, + "uri": "invocation.example.com/cosign-testing", + "event_id": "", + "builder.id": "" + }, + "scanner": { + "uri": "fakescanner.example.com/cosign-testing", + "version": "", + "db": { + "uri": "", + "version": "" + }, + "result": null + }, + "metadata": { + "scanStartedOn": "2022-04-12T00:00:00Z", + "scanFinishedOn": "2022-04-12T00:10:00Z" + } +} +` + ref, err := name.ParseReference(imgName) + if err != nil { + t.Fatal(err) + } + regOpts := options.RegistryOptions{} + ociremoteOpts, err := regOpts.ClientOpts(ctx) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 102; i++ { + vulnAttestationPath := filepath.Join(td, fmt.Sprintf("attestation-%d.vuln.json", i)) + if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil { + t.Fatal(err) + } + + // Attest to create a vuln attestation + attestCommand := attest.AttestCommand{ + KeyOpts: ko, + PredicatePath: vulnAttestationPath, + PredicateType: "vuln", + Timeout: 30 * time.Second, + Replace: false, + } + must(attestCommand.Exec(ctx, imgName), t) + } + + attOpts := options.AttestationDownloadOptions{} + _, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) + if err == nil { + t.Fatalf("Expected an error, but 'err' was 'nil'") + } + expectedError := "maximum number of attestations on an image is 100, found 102" + if err.Error() != expectedError { + t.Errorf("Exted the error to be: '%s' but it was '%s'", expectedError, err.Error()) + } +} + func TestAttestationReplace(t *testing.T) { repo, stop := reg(t) defer stop() @@ -1254,6 +1336,38 @@ func TestDuplicateSign(t *testing.T) { } } +func TestExcessiveSignatures(t *testing.T) { + repo, stop := reg(t) + defer stop() + td := t.TempDir() + + imgName := path.Join(repo, "cosign-e2e") + + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + ctx := context.Background() + + for i := 0; i < 102; i++ { + _, privKeyPath, _ := keypair(t, td) + + // Sign the image + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + so := options.SignOptions{ + Upload: true, + } + must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + } + err := download.SignatureCmd(ctx, options.RegistryOptions{}, imgName) + if err == nil { + t.Fatal("Expected an error, but 'err' was 'nil'") + } + expectedErr := "maximum number of signatures on an image is 100, found 102" + if err.Error() != expectedErr { + t.Fatalf("Expected the error '%s', but got the error '%s'", expectedErr, err.Error()) + } +} + func TestKeyURLVerify(t *testing.T) { // TODO: re-enable once distroless images are being signed by the new client t.Skip()