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

Check inactive shards for UUID for /retrieve endpoint #905

Merged
merged 2 commits into from
Jul 6, 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
94 changes: 44 additions & 50 deletions pkg/api/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"

"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/go-openapi/runtime"
Expand Down Expand Up @@ -312,43 +311,14 @@ func getEntryURL(locationURL url.URL, uuid string) strfmt.URI {

// GetLogEntryByUUIDHandler gets log entry and inclusion proof for specified UUID aka merkle leaf hash
func GetLogEntryByUUIDHandler(params entries.GetLogEntryByUUIDParams) middleware.Responder {
uuid, err := sharding.GetUUIDFromIDString(params.EntryUUID)
logEntry, err := retrieveLogEntry(params.HTTPRequest.Context(), params.EntryUUID)
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("could not get UUID from ID string %v", params.EntryUUID))
}
tidString, err := sharding.GetTreeIDFromIDString(params.EntryUUID)

// If treeID is found in EntryID, route to correct tree
if err == nil {
tid, err := strconv.ParseInt(tidString, 16, 64)
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("could not convert treeID %v to int", tidString))
}
logEntry, err := RetrieveUUID(params, uuid, tid)
if err != nil {
if errors.Is(err, ErrNotFound) {
return handleRekorAPIError(params, http.StatusNotFound, err, "")
}
return handleRekorAPIError(params, http.StatusInternalServerError, err, "")
}
return entries.NewGetLogEntryByUUIDOK().WithPayload(logEntry)
}

// If EntryID is plain UUID (ex. from client v0.5), check all trees
if errors.Is(err, sharding.ErrPlainUUID) {
trees := []sharding.LogRange{{TreeID: api.logRanges.ActiveTreeID()}}
trees = append(trees, api.logRanges.GetInactive()...)

for _, t := range trees {
logEntry, err := RetrieveUUID(params, uuid, t.TreeID)
if err != nil {
continue
}
return entries.NewGetLogEntryByUUIDOK().WithPayload(logEntry)
if _, ok := (err).(types.ValidationError); ok {
return handleRekorAPIError(params, http.StatusBadRequest, err, "incorrectly formatted uuid %s", params.EntryUUID)
}
return handleRekorAPIError(params, http.StatusNotFound, err, "UUID not found in any known trees")
return handleRekorAPIError(params, http.StatusInternalServerError, err, "ID %s not found in any known trees", params.EntryUUID)
}
return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("could not get treeID from ID string %v", params.EntryUUID))
return entries.NewGetLogEntryByUUIDOK().WithPayload(logEntry)
}

// SearchLogQueryHandler searches log by index, UUID, or proposed entry and returns array of entries found with inclusion proofs
Expand All @@ -366,17 +336,11 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("could not get UUID from ID string %v", entryID))
}
if tid, err := sharding.TreeID(entryID); err == nil {
entry, err := RetrieveUUID(entries.GetLogEntryByUUIDParams{
EntryUUID: entryID,
HTTPRequest: params.HTTPRequest,
}, uuid, tid)
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("could not get uuid from %v", entryID))
}
resultPayload = append(resultPayload, entry)
if logEntry, err := retrieveLogEntry(httpReqCtx, entryID); err == nil {
resultPayload = append(resultPayload, logEntry)
continue
}
// If we couldn't get the entry, search for the hash later
hash, err := hex.DecodeString(uuid)
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, malformedUUID)
Expand Down Expand Up @@ -487,16 +451,46 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo

var ErrNotFound = errors.New("grpc returned 0 leaves with success code")

// Attempt to retrieve a UUID from a backend tree
func RetrieveUUID(params entries.GetLogEntryByUUIDParams, uuid string, tid int64) (models.LogEntry, error) {
ctx := params.HTTPRequest.Context()
// Retrieve a Log Entry
// If a tree ID is specified, look in that tree
// Otherwise, look through all inactive and active shards
func retrieveLogEntry(ctx context.Context, entryUUID string) (models.LogEntry, error) {
uuid, err := sharding.GetUUIDFromIDString(entryUUID)
if err != nil {
return models.LogEntry{}, sharding.ErrPlainUUID
}

// Get the tree ID and check that shard for the entry
tid, err := sharding.TreeID(entryUUID)
if err == nil {
return retrieveUUIDFromTree(ctx, uuid, tid)
}

// If we got a UUID instead of an EntryID, search all shards
if errors.Is(err, sharding.ErrPlainUUID) {
trees := []sharding.LogRange{{TreeID: api.logRanges.ActiveTreeID()}}
trees = append(trees, api.logRanges.GetInactive()...)

for _, t := range trees {
logEntry, err := retrieveUUIDFromTree(ctx, uuid, t.TreeID)
if err != nil {
continue
}
return logEntry, nil
}
}

return models.LogEntry{}, err
}

func retrieveUUIDFromTree(ctx context.Context, uuid string, tid int64) (models.LogEntry, error) {
hashValue, err := hex.DecodeString(uuid)
if err != nil {
return models.LogEntry{}, err
return models.LogEntry{}, types.ValidationError(err)
}

tc := NewTrillianClientFromTreeID(params.HTTPRequest.Context(), tid)
log.RequestIDLogger(params.HTTPRequest).Debugf("Attempting to retrieve UUID %v from TreeID %v", uuid, tid)
tc := NewTrillianClientFromTreeID(ctx, tid)
log.Logger.Debugf("Attempting to retrieve UUID %v from TreeID %v", uuid, tid)

resp := tc.getLeafAndProofByHash(hashValue)
switch resp.status {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ func TestGetNonExistantIndex(t *testing.T) {
func TestGetNonExistantUUID(t *testing.T) {
// this uuid is extremely likely to not exist
out := runCliErr(t, "get", "--uuid", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
outputContains(t, out, "404")
outputContains(t, out, "400")
}

func TestEntryUpload(t *testing.T) {
Expand Down
7 changes: 6 additions & 1 deletion tests/sharding-e2e-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,14 @@ fi
echo
echo "Testing /api/v1/log/entries/retrieve endpoint..."

UUID1=$($REKOR_CLI get --log-index 0 --rekor_server http://localhost:3000 --format json | jq -r .UUID)
UUID1=$($REKOR_CLI get --log-index 1 --rekor_server http://localhost:3000 --format json | jq -r .UUID)
UUID2=$($REKOR_CLI get --log-index 3 --rekor_server http://localhost:3000 --format json | jq -r .UUID)


# Make sure retrieve by UUID in the inactive shard works
NUM_ELEMENTS=$(curl -f http://localhost:3000/api/v1/log/entries/retrieve -H "Content-Type: application/json" -H "Accept: application/json" -d "{ \"entryUUIDs\": [\"$UUID1\"]}" | jq '. | length')
stringsMatch $NUM_ELEMENTS "1"

HEX_INITIAL_TREE_ID=$(printf "%x" $INITIAL_TREE_ID | awk '{ for(c = 0; c < 16 ; c++) s = s"0"; s = s$1; print substr(s, 1 + length(s) - 16);}')
HEX_INITIAL_SHARD_ID=$(printf "%x" $SHARD_TREE_ID | awk '{ for(c = 0; c < 16 ; c++) s = s"0"; s = s$1; print substr(s, 1 + length(s) - 16);}')

Expand Down