-
Notifications
You must be signed in to change notification settings - Fork 165
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 inactive trees for GET by UUID requests #750
Conversation
pkg/api/entries.go
Outdated
// temporary workaround while users update to client v0.6, which has correct routing based on | ||
// tree IDs. if entry is not found in currently active tree, tries again to retrieve entry | ||
// from previously active tree. | ||
formerTID, err := strconv.ParseInt("3904496407287907110", 10, 64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of hardcoding the tree ID, could we go through all inactive shards in ranges
and see if the UUID exists in them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea! Done in updated version.
pkg/api/entries.go
Outdated
@@ -303,7 +303,24 @@ func GetLogEntryByUUIDHandler(params entries.GetLogEntryByUUIDParams) middleware | |||
switch resp.status { | |||
case codes.OK: | |||
case codes.NotFound: | |||
return handleRekorAPIError(params, http.StatusNotFound, fmt.Errorf("grpc error: %w", resp.err), "") | |||
// TODO: remove later |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of doing this logic here, I think it would make more sense in this conditional where we don't have a tree ID:
https://github.com/sigstore/rekor/blob/main/pkg/api/entries.go#L283-L290
so basically, if we don't have a tree id:
- Check the active tree ID
- If that fails, start going through each inactive shard & check those.
- If none of those work, return an error
Some notes on the new version:
I don't like returning the error from |
Codecov Report
@@ Coverage Diff @@
## main #750 +/- ##
==========================================
+ Coverage 49.02% 49.11% +0.08%
==========================================
Files 61 61
Lines 5566 5566
==========================================
+ Hits 2729 2734 +5
+ Misses 2542 2538 -4
+ Partials 295 294 -1
Continue to review full report at Codecov.
|
pkg/api/entries.go
Outdated
// Attempt to retrieve a UUID from a backend tree | ||
// TODO: don't return a bool that specifies whether it is an error, but instead check | ||
// the middleware.Responder to see if it is an error type (how?) | ||
func AttemptRetrieveUUID(params entries.GetLogEntryByUUIDParams, uuid string, tid int64) (middleware.Responder, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to not return the bool
here (it's just a marker for whether this is an error state) - is there some way to check a middleware.Responder
to see if it's an error type? Or should we implement something? I wanted to throw this up quickly so didn't go down this path of investigation. Could also return a separate error
but it would be great if even that is not necessary.
pkg/api/entries.go
Outdated
// Attempt to retrieve a UUID from a backend tree | ||
// TODO: don't return a bool that specifies whether it is an error, but instead check | ||
// the middleware.Responder to see if it is an error type (how?) | ||
func AttemptRetrieveUUID(params entries.GetLogEntryByUUIDParams, uuid string, tid int64) (middleware.Responder, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I would just call this RetrieveUUID
, it's implied that we're attempting to do so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair :)
pkg/api/entries.go
Outdated
case codes.NotFound: | ||
return handleRekorAPIError(params, http.StatusNotFound, fmt.Errorf("grpc error: %w", resp.err), "") | ||
// If there is an error, see if it is because the EntryID is a UUID and if so, | ||
// handle it | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The switch statement within the switch statement is a little hard to read. How about a structure like this:
tidString, err := sharding.GetTreeIDFromIDString(params.EntryUUID)
if err == nil {
// Get the UUID from the specified TreeID
}
// This should get the active tree ID and the inactive IDs
trees, err := GetTrees()
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, "")
}
for _, t := range trees {
logEntry, err := RetrieveUUID(params, uuid, t)
if err != nil {
continue
}
return entries.NewGetLogEntryByUUIDOK().WithPayload(logEntry)
}
// If we've reached this point, none of the shards have worked out, so return an error
return handleRekorAPIError(params, http.StatusBadRequest, fmt.Errorf("unable to find UUID"), "")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to be more like this, let me know what you think about the readability.
pkg/api/entries.go
Outdated
switch err.Error() { | ||
// If EntryID is plain UUID (ex. from client v0.5), first try the active tree | ||
case "cannot get treeID from plain UUID": | ||
tid := api.logRanges.ActiveTreeID() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't GetTrees
include the active Tree ID?
pkg/api/api.go
Outdated
@@ -66,18 +66,41 @@ type API struct { | |||
certChainPem string // PEM encoded timestamping cert chain | |||
} | |||
|
|||
func NewAPI(ranges sharding.LogRanges, treeID uint) (*API, error) { | |||
func GetTrees() (*trillian.ListTreesResponse, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be getting this list of trees from the config and the tlog_id
flag, rather than from the server. Right now, this will return all trees, but it doesn't make sense to check them if the user hasn't specified them in the config.
Thanks for the good comments @priyawadhwa , will incorporate these and get a new one up. |
@priyawadhwa It's ready! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the speedy changes @lkatalin ! Just a couple more comments and we should be good to go :)
pkg/api/entries.go
Outdated
return handleRekorAPIError(params, http.StatusNotFound, fmt.Errorf("grpc error: %w", resp.err), "") | ||
default: | ||
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.err), trillianUnexpectedResult) | ||
resp, _ := RetrieveUUID(params, uuid, tid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably shouldn't hide this error, might be useful to return it for the user if it's non-nil
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense now when returning (models.LogEntry, error)
from RetrieveUUID
pkg/api/entries.go
Outdated
return handleRekorAPIError(params, http.StatusNotFound, errors.New("grpc returned 0 leaves with success code"), "") | ||
} | ||
// If EntryID is plain UUID (ex. from client v0.5), check all trees | ||
if err.Error() == "cannot get treeID from plain UUID" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best practice would be not to hardcode this string in case someone changes the wording on the error later on. You could either:
- Create an error type to check for (example here which uses
errors.Is
) - Change the
GetUUIDFromIDString
function to return"", nil
in the case that there is no treeID. That way the error is nil, but we know if the treeID is empty that we have to go through all the shards
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, these options make more sense.
pkg/api/entries.go
Outdated
// Attempt to retrieve a UUID from a backend tree | ||
func RetrieveUUID(params entries.GetLogEntryByUUIDParams, uuid string, tid int64) (middleware.Responder, error) { | ||
ctx := params.HTTPRequest.Context() | ||
hashValue, _ := hex.DecodeString(uuid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should check this error in case someone passes in a malformed UUID?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just moved this line of code from where it was earlier, but checking it seems like a good thing to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks ! :)
pkg/api/entries.go
Outdated
@@ -438,3 +421,36 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo | |||
|
|||
return entries.NewSearchLogQueryOK().WithPayload(resultPayload) | |||
} | |||
|
|||
// Attempt to retrieve a UUID from a backend tree | |||
func RetrieveUUID(params entries.GetLogEntryByUUIDParams, uuid string, tid int64) (middleware.Responder, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would still make more sense to return (models.LogEntry,, error)
here and create the response in GetLogEntryByIndexHandler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like creating a models.LogEntry
will require turning a trillian.GetEntryAndProofResponse
into a models.LogEntryAnon
, is that right? Is there a straightforward way to do that? I'm looking at logEntryFromLeaf
as an example but not really seeing how the fields match up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make a LogEntryAnon
and unmarshal into it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think logEntryFromLeaf
returns models.LogEntry
, so after line 443 you could just return logEntry, nil
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! You're so right. Thanks!
276f7bb
to
aa7e7b6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple small comments!
pkg/api/entries.go
Outdated
@@ -438,3 +427,39 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo | |||
|
|||
return entries.NewSearchLogQueryOK().WithPayload(resultPayload) | |||
} | |||
|
|||
var ErrNotFound = errors.New("grpc returned 0 leaves with success code") | |||
var ErrUnexpected = errors.New("unexpected error") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this ErrUnexpected
since we don't check for it anywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay cool, I took it out!
pkg/api/entries.go
Outdated
|
||
logEntry, err := logEntryFromLeaf(ctx, api.signer, tc, leaf, result.SignedLogRoot, result.Proof, tid, api.logRanges) | ||
if err != nil { | ||
return models.LogEntry{}, ErrUnexpected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as mentioned above we can just return err
here
Signed-off-by: Lily Sturmann <lsturman@redhat.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! 🎉
UPDATED:
This fix is useful for client v0.5 when there is more than one shard. When a GET by UUID request is made with a UUID (as opposed to the EntryID that has the prepended TreeID), this change will first check the active tree, then iterate through inactive trees to see if the UUID can be found in any of them.
Fixes the "expected bug" mentioned in #711
Add temporary fix to check old prod treeID for entry if UUID is notfound in active tree. This can be removed once client v0.5 is phased
out.
Related to #711 (the "expected bug") - this fix is only needed if we plan to shard the production server before the clientv0.5
is phased out.