Skip to content

Commit

Permalink
Add logic to GET artifacts via old or new UUID (#587)
Browse files Browse the repository at this point in the history
* Move range.go into sharding package to avoid import cycles

Signed-off-by: Lily Sturmann <lsturman@redhat.com>

* Change name of FullID to EntryID

Signed-off-by: Lily Sturmann <lsturman@redhat.com>

* Add unit tests for sharding package

Also add a few helper functions and update names.

Signed-off-by: Lily Sturmann <lsturman@redhat.com>

* Add logic to GET artifacts via old UUID or new EntryID

Signed-off-by: Lily Sturmann <lsturman@redhat.com>

* Add e2e test for longer EntryID

Signed-off-by: Lily Sturmann <lsturman@redhat.com>
  • Loading branch information
lkatalin authored Jan 25, 2022
1 parent fabfe0b commit adab5c5
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 48 deletions.
17 changes: 13 additions & 4 deletions cmd/rekor-cli/app/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/sharding"
"github.com/sigstore/rekor/pkg/types"
)

Expand Down Expand Up @@ -84,6 +85,11 @@ var getCmd = &cobra.Command{
}

logIndex := viper.GetString("log-index")
uuid := viper.GetString("uuid")
if logIndex == "" && uuid == "" {
return nil, errors.New("either --uuid or --log-index must be specified")
}

if logIndex != "" {
params := entries.NewGetLogEntryByIndexParams()
params.SetTimeout(viper.GetDuration("timeout"))
Expand All @@ -106,19 +112,22 @@ var getCmd = &cobra.Command{
}
}

uuid := viper.GetString("uuid")
if uuid != "" {
params := entries.NewGetLogEntryByUUIDParams()
params.SetTimeout(viper.GetDuration("timeout"))
params.EntryUUID = uuid

params.EntryUUID, err = sharding.GetUUIDFromIDString(uuid)
if err != nil {
return nil, fmt.Errorf("unable to parse uuid: %w", err)
}

resp, err := rekorClient.Entries.GetLogEntryByUUID(params)
if err != nil {
return nil, err
}

for k, entry := range resp.Payload {
if k != uuid {
if k != params.EntryUUID {
continue
}

Expand All @@ -130,7 +139,7 @@ var getCmd = &cobra.Command{
}
}

return nil, errors.New("either --uuid or --log-index must be specified")
return nil, errors.New("entry not found")
}),
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/rekor-cli/app/pflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ func validateFileOrURL(v string) error {
return valGen().Set(v)
}

// validateID ensures the ID is either a FullID (TreeID + UUID) or a UUID
// validateID ensures the ID is either an EntryID (TreeID + UUID) or a UUID
func validateID(v string) error {
if len(v) != sharding.FullIDHexStringLen && len(v) != sharding.UUIDHexStringLen {
return fmt.Errorf("ID len error, expected %v (FullID) or %v (UUID) but got len %v for ID %v", sharding.FullIDHexStringLen, sharding.UUIDHexStringLen, len(v), v)
if len(v) != sharding.EntryIDHexStringLen && len(v) != sharding.UUIDHexStringLen {
return fmt.Errorf("ID len error, expected %v (EntryID) or %v (UUID) but got len %v for ID %v", sharding.EntryIDHexStringLen, sharding.UUIDHexStringLen, len(v), v)
}

if err := validateString("required,hexadecimal")(v); err != nil {
Expand Down
14 changes: 7 additions & 7 deletions cmd/rekor-server/app/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,27 @@ import (
"strconv"
"strings"

"github.com/sigstore/rekor/pkg/api"
"github.com/sigstore/rekor/pkg/sharding"
)

type LogRangesFlag struct {
Ranges api.LogRanges
Ranges sharding.LogRanges
}

func (l *LogRangesFlag) Set(s string) error {
ranges := strings.Split(s, ",")
l.Ranges = api.LogRanges{}
l.Ranges = sharding.LogRanges{}

var err error
inputRanges := []api.LogRange{}
inputRanges := []sharding.LogRange{}

// Only go up to the second to last one, the last one is special cased beow
for _, r := range ranges[:len(ranges)-1] {
split := strings.SplitN(r, "=", 2)
if len(split) != 2 {
return fmt.Errorf("invalid range flag, expected two parts separated by an =, got %s", r)
}
lr := api.LogRange{}
lr := sharding.LogRange{}
lr.TreeID, err = strconv.ParseUint(split[0], 10, 64)
if err != nil {
return err
Expand All @@ -60,7 +60,7 @@ func (l *LogRangesFlag) Set(s string) error {
return err
}

inputRanges = append(inputRanges, api.LogRange{
inputRanges = append(inputRanges, sharding.LogRange{
TreeID: lastTreeID,
})

Expand All @@ -73,7 +73,7 @@ func (l *LogRangesFlag) Set(s string) error {
TreeIDs[lr.TreeID] = struct{}{}
}

l.Ranges = api.LogRanges{
l.Ranges = sharding.LogRanges{
Ranges: inputRanges,
}
return nil
Expand Down
8 changes: 4 additions & 4 deletions cmd/rekor-server/app/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/sigstore/rekor/pkg/api"
"github.com/sigstore/rekor/pkg/sharding"
)

func TestLogRanges_Set(t *testing.T) {
tests := []struct {
name string
arg string
want []api.LogRange
want []sharding.LogRange
active uint64
}{
{
name: "one, no length",
arg: "1234",
want: []api.LogRange{
want: []sharding.LogRange{
{
TreeID: 1234,
TreeLength: 0,
Expand All @@ -43,7 +43,7 @@ func TestLogRanges_Set(t *testing.T) {
{
name: "two",
arg: "1234=10,7234",
want: []api.LogRange{
want: []sharding.LogRange{
{
TreeID: 1234,
TreeLength: 10,
Expand Down
7 changes: 4 additions & 3 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (

"github.com/sigstore/rekor/pkg/log"
pki "github.com/sigstore/rekor/pkg/pki/x509"
"github.com/sigstore/rekor/pkg/sharding"
"github.com/sigstore/rekor/pkg/signer"
"github.com/sigstore/rekor/pkg/storage"
"github.com/sigstore/sigstore/pkg/cryptoutils"
Expand All @@ -56,7 +57,7 @@ func dial(ctx context.Context, rpcServer string) (*grpc.ClientConn, error) {
type API struct {
logClient trillian.TrillianLogClient
logID int64
logRanges *LogRanges
logRanges *sharding.LogRanges
pubkey string // PEM encoded public key
pubkeyHash string // SHA256 hash of DER-encoded public key
signer signature.Signer
Expand All @@ -65,7 +66,7 @@ type API struct {
certChainPem string // PEM encoded timestamping cert chain
}

func NewAPI(ranges LogRanges) (*API, error) {
func NewAPI(ranges sharding.LogRanges) (*API, error) {
logRPCServer := fmt.Sprintf("%s:%d",
viper.GetString("trillian_log_server.address"),
viper.GetUint("trillian_log_server.port"))
Expand Down Expand Up @@ -156,7 +157,7 @@ var (
storageClient storage.AttestationStorage
)

func ConfigureAPI(ranges LogRanges) {
func ConfigureAPI(ranges sharding.LogRanges) {
cfg := radix.PoolConfig{}
var err error

Expand Down
8 changes: 7 additions & 1 deletion pkg/api/entries.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/generated/restapi/operations/entries"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/sharding"
"github.com/sigstore/rekor/pkg/types"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"
Expand Down Expand Up @@ -266,7 +267,12 @@ 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 {
ctx := params.HTTPRequest.Context()
hashValue, _ := hex.DecodeString(params.EntryUUID)

entryUUID, err := sharding.GetUUIDFromIDString(params.EntryUUID)
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, "")
}
hashValue, _ := hex.DecodeString(entryUUID)
tc := NewTrillianClient(params.HTTPRequest.Context())

resp := tc.getLeafAndProofByHash(hashValue)
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/ranges.go → pkg/sharding/ranges.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package api
package sharding

type LogRanges struct {
Ranges []LogRange
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/ranges_test.go → pkg/sharding/ranges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package api
package sharding

import "testing"

Expand Down
85 changes: 61 additions & 24 deletions pkg/sharding/sharding.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,71 +19,108 @@ import (
"encoding/hex"
"fmt"
"strconv"

"github.com/sigstore/rekor/pkg/api"
)

// A FullID refers to a specific artifact's ID and is made of two components,
// An EntryID refers to a specific artifact's ID and is made of two components,
// the TreeID and the UUID. The TreeID is a hex-encoded uint64 (8 bytes)
// referring to the specific trillian tree (also known as log or shard) where
// the artifact can be found. The UUID is a hex-encoded 32-byte number
// referring to the artifact's merkle leaf hash from trillian. Artifact lookup
// by UUID occurs by finding the UUID within the tree specified by the TreeID.
//
// A FullID is 40 bytes long and looks like this:
// An EntryID is 40 bytes long and looks like this:
// FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
// |_______ ________| |_____________________________________ ______________________________________|
// \/ \/
// TreeID (8 bytes, hex) UUID (32 bytes, hex)

const TreeIDHexStringLen = 16
const UUIDHexStringLen = 64
const FullIDHexStringLen = TreeIDHexStringLen + UUIDHexStringLen
const EntryIDHexStringLen = TreeIDHexStringLen + UUIDHexStringLen

// TODO: replace this with the actual LogRanges struct when logic is hooked up
var dummy = api.LogRanges{
Ranges: []api.LogRange{},
var dummyLogRanges = LogRanges{
Ranges: []LogRange{
{
TreeID: 0,
TreeLength: 0}},
}

type FullID struct {
type EntryID struct {
TreeID string
UUID string
}

func CreateFullID(treeid string, uuid string) (FullID, error) {
if len(treeid) != TreeIDHexStringLen {
// This function can take a TreeID of equal or greater length than TreeIDHexStringLen. In
// case the TreeID length is less than TreeIDHexStringLen, it will be padded to the correct
// length.
func CreateEntryIDFromParts(treeid string, uuid string) (EntryID, error) {
if len(treeid) > TreeIDHexStringLen {
err := fmt.Errorf("invalid treeid len: %v", len(treeid))
return createEmptyFullID(), err
return createEmptyEntryID(), err
}

if len(uuid) != UUIDHexStringLen {
err := fmt.Errorf("invalid uuid len: %v", len(uuid))
return createEmptyFullID(), err
return createEmptyEntryID(), err
}

treeidFormatted, err := PadToTreeIDLen(treeid)
if err != nil {
return createEmptyEntryID(), err
}

if _, err := hex.DecodeString(treeid); err != nil {
err := fmt.Errorf("treeid is not a valid hex string: %v", treeid)
return createEmptyFullID(), err
if _, err := hex.DecodeString(treeidFormatted); err != nil {
err := fmt.Errorf("treeid %v is not a valid hex string: %v", treeidFormatted, err)
return createEmptyEntryID(), err
}

if _, err := hex.DecodeString(uuid); err != nil {
err := fmt.Errorf("uuid is not a valid hex string: %v", uuid)
return createEmptyFullID(), err
err := fmt.Errorf("uuid %v is not a valid hex string: %v", uuid, err)
return createEmptyEntryID(), err
}

return FullID{
TreeID: treeid,
return EntryID{
TreeID: treeidFormatted,
UUID: uuid}, nil
}

func createEmptyFullID() FullID {
return FullID{
func createEmptyEntryID() EntryID {
return EntryID{
TreeID: "",
UUID: ""}
}

func PrependActiveTreeID(uuid string) (FullID, error) {
func CreateEntryIDWithActiveTreeID(uuid string) (EntryID, error) {
// TODO: Update this to be the global LogRanges struct
active := dummy.ActiveIndex()
return CreateFullID(strconv.FormatUint(active, 10), uuid)
treeid := strconv.FormatUint(dummyLogRanges.ActiveIndex(), 10)
return CreateEntryIDFromParts(treeid, uuid)
}

func (e EntryID) ReturnEntryIDString() string {
return e.TreeID + e.UUID
}

func PadToTreeIDLen(t string) (string, error) {
switch {
case len(t) == TreeIDHexStringLen:
return t, nil
case len(t) > TreeIDHexStringLen:
return "", fmt.Errorf("invalid treeID %v: too long", t)
default:
return fmt.Sprintf("%016s", t), nil
}
}

// Returns UUID (with no prepended TreeID) from a UUID or EntryID string
func GetUUIDFromIDString(id string) (string, error) {
if len(id) != UUIDHexStringLen && len(id) != EntryIDHexStringLen {
return "", fmt.Errorf("invalid ID len %v for %v", len(id), id)
}

if _, err := hex.DecodeString(id); err != nil {
return "", fmt.Errorf("id %v is not a valid hex string: %v", id, err)
}

return id[len(id)-UUIDHexStringLen:], nil
}
Loading

0 comments on commit adab5c5

Please sign in to comment.