Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bounds on number of elements in api/v1/log/entries/retrieve #1011

Merged
merged 3 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -519,21 +519,24 @@ definitions:
properties:
entryUUIDs:
type: array
minItems: 1
maxItems: 10
items:
type: string
minItems: 1
pattern: '^([0-9a-fA-F]{64}|[0-9a-fA-F]{80})$'
logIndexes:
type: array
minItems: 1
maxItems: 10
items:
type: integer
minimum: 0
entries:
type: array
minItems: 1
maxItems: 10
items:
$ref: '#/definitions/ProposedEntry'
minItems: 1

LogInfo:
type: object
Expand Down
9 changes: 9 additions & 0 deletions pkg/api/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ import (
"github.com/sigstore/sigstore/pkg/signature/options"
)

const (
maxSearchQueries = 10
)

func signEntry(ctx context.Context, signer signature.Signer, entry models.LogEntryAnon) ([]byte, error) {
payload, err := entry.MarshalBinary()
if err != nil {
Expand Down Expand Up @@ -316,6 +320,11 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo
resultPayload := []models.LogEntry{}
tc := NewTrillianClient(httpReqCtx)

totalQueries := len(params.Entry.EntryUUIDs) + len(params.Entry.Entries()) + len(params.Entry.LogIndexes)
if totalQueries > maxSearchQueries {
return handleRekorAPIError(params, http.StatusUnprocessableEntity, fmt.Errorf(maxSearchQueryLimit, maxSearchQueries), fmt.Sprintf(maxSearchQueryLimit, maxSearchQueries))
}

if len(params.Entry.EntryUUIDs) > 0 || len(params.Entry.Entries()) > 0 {
g, _ := errgroup.WithContext(httpReqCtx)

Expand Down
1 change: 1 addition & 0 deletions pkg/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
sthGenerateError = "Error generating signed tree head"
unsupportedPKIFormat = "The PKI format requested is not supported by this server"
unexpectedInactiveShardError = "Unexpected error communicating with inactive shard"
maxSearchQueryLimit = "more than max allowed %d entries in request"
)

func errorMsg(message string, code int) *models.Error {
Expand Down
27 changes: 27 additions & 0 deletions pkg/generated/models/search_log_query.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions pkg/generated/restapi/embedded_spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions tests/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1168,3 +1168,54 @@ func TestHostnameInSTH(t *testing.T) {
t.Errorf("logInfo contains rekor.sigstore.dev which should not be set by default")
}
}

func TestSearchQueryLimit(t *testing.T) {
tests := []struct {
description string
limit int
shouldErr bool
}{
{
description: "request 6 entries",
limit: 6,
}, {
description: "request 10 entries",
limit: 10,
}, {
description: "request more than max",
limit: 12,
shouldErr: true,
},
}

for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
b := bytes.NewReader(getBody(t, test.limit))
resp, err := http.Post("http://localhost:3000/api/v1/log/entries/retrieve", "application/json", b)
if err != nil {
t.Fatal(err)
}
c, _ := ioutil.ReadAll(resp.Body)
t.Log(string(c))
if resp.StatusCode != 200 && !test.shouldErr {
t.Fatalf("expected test to pass but it failed")
}
if resp.StatusCode != 422 && test.shouldErr {
t.Fatal("expected test to fail but it passed")
}
if test.shouldErr && !strings.Contains(string(c), "logIndexes in body should have at most 10 items") {
t.Fatal("expected max limit error but didn't get it")
}
})
}
}

func getBody(t *testing.T, limit int) []byte {
t.Helper()
s := fmt.Sprintf("{\"logIndexes\": [%d", limit)
for i := 1; i < limit; i++ {
s = fmt.Sprintf("%s, %d", s, i)
}
s += "]}"
return []byte(s)
}