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

Search through all shards when searching by hash #1082

Merged
merged 5 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ rekorServerImagerefs
rekorCliImagerefs
trillianServerImagerefs
trillianSignerImagerefs
cosign.*
signature
rekor.pub
45 changes: 29 additions & 16 deletions pkg/api/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,6 @@ func GetLogEntryByUUIDHandler(params entries.GetLogEntryByUUIDParams) middleware
func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Responder {
httpReqCtx := params.HTTPRequest.Context()
resultPayload := []models.LogEntry{}
tc := NewTrillianClient(httpReqCtx)

totalQueries := len(params.Entry.EntryUUIDs) + len(params.Entry.Entries()) + len(params.Entry.LogIndexes)
if totalQueries > maxSearchQueries {
Expand Down Expand Up @@ -415,23 +414,34 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo
searchHashes = append(searchHashes, hash)
}

searchByHashResults := make([]*trillian.GetEntryAndProofResponse, len(searchHashes))
searchByHashResults := make([]map[int64]*trillian.GetEntryAndProofResponse, len(searchHashes))
g, _ = errgroup.WithContext(httpReqCtx)
for i, hash := range searchHashes {
i, hash := i, hash // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
resp := tc.getLeafAndProofByHash(hash)
switch resp.status {
case codes.OK:
case codes.NotFound:
code = http.StatusNotFound
return resp.err
default:
var results map[int64]*trillian.GetEntryAndProofResponse
for _, shard := range api.logRanges.AllShards() {
tcs := NewTrillianClientFromTreeID(httpReqCtx, shard)
resp := tcs.getLeafAndProofByHash(hash)
if resp.status != codes.OK {
continue
}
if resp.err != nil {
continue
}
leafResult := resp.getLeafAndProofResult
if leafResult != nil && leafResult.Leaf != nil {
if results == nil {
results = map[int64]*trillian.GetEntryAndProofResponse{}
}
results[shard] = resp.getLeafAndProofResult
}
}
leafResult := resp.getLeafAndProofResult
if leafResult != nil && leafResult.Leaf != nil {
searchByHashResults[i] = leafResult
if results == nil {
code = http.StatusNotFound
return fmt.Errorf("no responses found")
priyawadhwa marked this conversation as resolved.
Show resolved Hide resolved
}
searchByHashResults[i] = results
return nil
})
}
Expand All @@ -440,14 +450,17 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo
return handleRekorAPIError(params, code, err, err.Error())
}

for _, leafResp := range searchByHashResults {
if leafResp != nil {
logEntry, err := logEntryFromLeaf(httpReqCtx, api.signer, tc, leafResp.Leaf, leafResp.SignedLogRoot, leafResp.Proof, api.logRanges.ActiveTreeID(), api.logRanges)
for _, hashMap := range searchByHashResults {
for shard, leafResp := range hashMap {
if leafResp == nil {
continue
}
tcs := NewTrillianClientFromTreeID(httpReqCtx, shard)
logEntry, err := logEntryFromLeaf(httpReqCtx, api.signer, tcs, leafResp.Leaf, leafResp.SignedLogRoot, leafResp.Proof, shard, api.logRanges)
if err != nil {
code = http.StatusInternalServerError
return handleRekorAPIError(params, code, err, err.Error())
}

resultPayload = append(resultPayload, logEntry)
}
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/sharding/ranges.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ func (l *LogRanges) NoInactive() bool {
return l.inactive == nil
}

// AllShards returns all shards, starting with the active shard and then the inactive shards
func (l *LogRanges) AllShards() []int64 {
shards := []int64{l.ActiveTreeID()}
for _, in := range l.GetInactive() {
shards = append(shards, in.TreeID)
}
return shards
}

// TotalInactiveLength returns the total length across all inactive shards;
// we don't know the length of the active shard.
func (l *LogRanges) TotalInactiveLength() int64 {
Expand Down
15 changes: 11 additions & 4 deletions tests/sharding-e2e-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ set -ex
echo "Installing createtree..."
go install github.com/google/trillian/cmd/createtree@latest

echo "Installing cosign..."
go install github.com/sigstore/cosign/cmd/cosign@latest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic may evolve as time goes on - are we better actually reimplementing the logic here by directly calling the rekor APIs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we probably want to test with cosign somewhere, whether it's here or in a prober. not super opinionated on this one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 let's remove cosign and use rekor: the problem was reproducible with a SearchLogQuery


echo "starting services"
docker-compose up -d
Expand All @@ -34,6 +36,7 @@ go build -o rekor-cli ./cmd/rekor-cli
REKOR_CLI=$(pwd)/rekor-cli
go build -o rekor-server ./cmd/rekor-server


function check_log_index () {
logIndex=$1
# make sure we can get this log index from rekor
Expand Down Expand Up @@ -99,10 +102,10 @@ trap "collectLogsOnFailure $?" EXIT
echo "Waiting for rekor server to come up..."
waitForRekorServer

# Add some things to the tlog :)
pushd tests
$REKOR_CLI upload --artifact test_file.txt --signature test_file.sig --public-key test_public_key.key --rekor_server http://localhost:3000
popd
# Sign a blob and push to the log with cosign
rm cosign.*
COSIGN_PASSWORD=pw cosign generate-key-pair
COSIGN_PASSWORD=pw COSIGN_EXPERIMENTAL=1 cosign sign-blob README.md --key cosign.key --rekor-url http://localhost:3000 --output-signature ./signature

# Make sure we can prove consistency
$REKOR_CLI loginfo --rekor_server http://localhost:3000
Expand Down Expand Up @@ -251,6 +254,10 @@ ENTRY_ID_2=$($REKOR_CLI get --log-index 3 --rekor_server http://localhost:3000 -
NUM_ELEMENTS=$(curl -f http://localhost:3000/api/v1/log/entries/retrieve -H "Content-Type: application/json" -H "Accept: application/json" -d "{ \"entryUUIDs\": [\"$ENTRY_ID_1\"]}" | jq '. | length')
stringsMatch $NUM_ELEMENTS "1"

# Make sure we can verify the blob we entered into the now-inactive shard
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can replace with a call to ./rekor verify --public-key blah --artifact blah --signature blah with the artifact properties

echo $NEW_PUB_KEY > rekor.pub
COSIGN_EXPERIMENTAL=1 SIGSTORE_REKOR_PUBLIC_KEY=./rekor.pub cosign verify-blob README.md --key cosign.pub --rekor-url http://localhost:3000 --signature ./signature

# -f makes sure we exit on failure
NUM_ELEMENTS=$(curl -f http://localhost:3000/api/v1/log/entries/retrieve -H "Content-Type: application/json" -H "Accept: application/json" -d "{ \"entryUUIDs\": [\"$ENTRY_ID_1\", \"$ENTRY_ID_2\"]}" | jq '. | length')
stringsMatch $NUM_ELEMENTS "2"
Expand Down