Skip to content
Snippets Groups Projects
Unverified Commit adab5c53 authored by Lily Sturmann's avatar Lily Sturmann Committed by GitHub
Browse files

Add logic to GET artifacts via old or new UUID (#587)


* Move range.go into sharding package to avoid import cycles

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

* Change name of FullID to EntryID

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

* Add unit tests for sharding package

Also add a few helper functions and update names.

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

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

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

* Add e2e test for longer EntryID

Signed-off-by: default avatarLily Sturmann <lsturman@redhat.com>
parent fabfe0b0
No related branches found
No related tags found
No related merge requests found
......@@ -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"
)
......@@ -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"))
......@@ -106,11 +112,14 @@ 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 {
......@@ -118,7 +127,7 @@ var getCmd = &cobra.Command{
}
for k, entry := range resp.Payload {
if k != uuid {
if k != params.EntryUUID {
continue
}
......@@ -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")
}),
}
......
......@@ -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 {
......
......@@ -20,19 +20,19 @@ 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] {
......@@ -40,7 +40,7 @@ func (l *LogRangesFlag) Set(s string) error {
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
......@@ -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,
})
......@@ -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
......
......@@ -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,
......@@ -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,
......
......@@ -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"
......@@ -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
......@@ -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"))
......@@ -156,7 +157,7 @@ var (
storageClient storage.AttestationStorage
)
func ConfigureAPI(ranges LogRanges) {
func ConfigureAPI(ranges sharding.LogRanges) {
cfg := radix.PoolConfig{}
var err error
......
......@@ -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"
......@@ -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)
......
......@@ -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
......
......@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package api
package sharding
import "testing"
......
......@@ -19,18 +19,16 @@ 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
// |_______ ________| |_____________________________________ ______________________________________|
// \/ \/
......@@ -38,52 +36,91 @@ import (
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
}
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sharding
import (
"strconv"
"testing"
)
// Create some test data
// Good data
const validTreeID1 = "FFFFFFFFFFFFFFFF"
const validTreeID2 = "0000000000000000"
const validTreeID3 = "7241b7903737211c"
const shortTreeID = "12345"
const validUUID = "f794467401d57241b7903737211c721cb3315648d077a9f02ceefb6e404a05de"
const validEntryID1 = validTreeID1 + validUUID
const validEntryID2 = validTreeID2 + validUUID
const validEntryID3 = validTreeID3 + validUUID
var validTreeIDs = []string{validTreeID1, validTreeID2, validTreeID3, shortTreeID}
var validEntryIDs = []string{validEntryID1, validEntryID2, validEntryID3}
// Bad data
// Wrong length
const tooLongTreeID = validTreeID1 + "e"
const tooLongUUID = validUUID + "e"
var tooShortUUID = validUUID[:len(validUUID)-1]
const tooLongEntryID = validEntryID1 + "e"
var tooShortEntryID = validEntryID1[:len(validEntryID1)-1]
var wrongLengthTreeIDs = []string{tooLongTreeID, validEntryID3, validUUID}
var wrongLengthUUIDs = []string{tooShortUUID, tooLongUUID, validEntryID3, validTreeID1}
var wrongLengthEntryandUUIDs = []string{tooLongEntryID, tooShortEntryID, tooLongUUID, tooShortUUID, validTreeID3}
// Not valid hex
const notHexTreeID1 = "ZZZZZZZZZZZZZZZZ"
const notHexTreeID2 = "FFFFFFF_FFFFFFFF"
const notHexTreeID3 = "xxFFFFFFFFFFFFFF"
const notHexUUID1 = "94467401d57241b7903737211c721cb3315648d077a9f02ceefb6e404a05dezq"
const notHexUUID2 = "y794467401d57241b7903737211c721cb3315648d077a9f02ceefb6e404a05de"
const notHexUUID3 = "f794467401d57241b7903737211c721cbp3315648d077a9f02ceefb6e404a05d"
const notHexEntryID1 = notHexTreeID1 + validUUID
const notHexEntryID2 = validTreeID2 + notHexUUID1
const notHexEntryID3 = notHexTreeID2 + notHexUUID3
var notHexTreeIDs = []string{notHexTreeID1, notHexTreeID2, notHexTreeID3}
var notHexUUIDs = []string{notHexUUID1, notHexUUID2, notHexUUID3}
var notHexEntryandUUIDs = []string{notHexEntryID1, notHexEntryID2, notHexEntryID3, notHexUUID1, notHexUUID2, notHexUUID3}
// Test functions
func TestCreateEntryID(t *testing.T) {
for _, s := range wrongLengthTreeIDs {
if _, err := CreateEntryIDFromParts(s, validUUID); err == nil {
t.Errorf("expected length error for wrong TreeID of invalid len: %v", s)
}
}
for _, s := range wrongLengthUUIDs {
if _, err := CreateEntryIDFromParts(validTreeID1, s); err == nil {
t.Errorf("expected length error for wrong UUID of invalid len: %v", s)
}
}
for _, s := range notHexTreeIDs {
if _, err := CreateEntryIDFromParts(s, validUUID); err == nil {
t.Errorf("expected hex error for TreeID: %v", s)
}
}
for _, s := range notHexUUIDs {
if _, err := CreateEntryIDFromParts(validTreeID3, s); err == nil {
t.Errorf("expected hex error for UUID: %v", s)
}
}
for _, tid := range validTreeIDs {
entryID, err := CreateEntryIDFromParts(tid, validUUID)
if err != nil {
t.Errorf("failed to create entryID from %v + %v: %v", tid, validUUID, err)
}
expectedTid, _ := PadToTreeIDLen(tid)
if entryID.TreeID != expectedTid {
t.Errorf("created entryID with incorrect treeID: expected %v, got %v", tid, entryID.TreeID)
}
if entryID.UUID != validUUID {
t.Errorf("created entryID with incorrect UUID: expected %v, got %v", validUUID, entryID.UUID)
}
}
}
func TestCreateEmptyEntryID(t *testing.T) {
emptyEntryID := createEmptyEntryID()
if emptyEntryID.TreeID != "" {
t.Errorf("expected empty EntryID.TreeID but got %v", emptyEntryID.TreeID)
}
if emptyEntryID.UUID != "" {
t.Errorf("expected empty EntryID.UUID but got %v", emptyEntryID.UUID)
}
}
func TestCreateEntryIDWithActiveTreeID(t *testing.T) {
entryID, err := CreateEntryIDWithActiveTreeID(validUUID)
if err != nil {
t.Errorf("unable to create entryID: %v", err)
}
// TODO: Update dummy to be the global LogRanges struct
activeIndexString := strconv.FormatUint(dummyLogRanges.ActiveIndex(), 10)
expectedTreeID, err := PadToTreeIDLen(activeIndexString)
if err != nil {
t.Errorf("unable to pad %v to treeIDLen: %v", activeIndexString, err)
}
if entryID.TreeID != expectedTreeID {
t.Errorf("expected entryID.TreeID %v but got %v", dummyLogRanges.ActiveIndex(), entryID.TreeID)
}
if entryID.UUID != validUUID {
t.Errorf("expected entryID.TreeID %v but got %v", validUUID, entryID.UUID)
}
}
func TestPadToTreeIDLen(t *testing.T) {
short := "12345678"
shortPadded := "0000000012345678"
medium := "1234567812345678"
long := "12345678901234567890"
result1, err1 := PadToTreeIDLen(short)
if result1 != shortPadded || err1 != nil {
t.Errorf("error padding %v: expected (%v, nil), got (%v, %v)", short, shortPadded, result1, err1)
}
result2, err2 := PadToTreeIDLen(medium)
if result2 != medium || err2 != nil {
t.Errorf("error padding %v: expected (%v, nil), got (%v, %v)", medium, medium, result2, err2)
}
result3, err3 := PadToTreeIDLen(long)
if result3 != "" || err3 == nil {
t.Errorf("expected error in padding %v, but got %v", long, result3)
}
}
func TestReturnEntryIDString(t *testing.T) {
entryID, _ := CreateEntryIDFromParts(validTreeID1, validUUID)
IDString := entryID.ReturnEntryIDString()
if IDString != validEntryID1 {
t.Errorf("expected entryID string %v but got %v", validEntryID1, IDString)
}
}
func TestGetUUIDFromIDString(t *testing.T) {
for _, s := range wrongLengthEntryandUUIDs {
// TODO: check for correct error
if _, err := GetUUIDFromIDString(s); err == nil {
t.Errorf("expected length error for GetUUIDFromIDString(%v) but no error was found", s)
}
}
for _, s := range notHexEntryandUUIDs {
// TODO: check for correct error
if _, err := GetUUIDFromIDString(s); err == nil {
t.Errorf("expected invalid hex error for GetUUIDFromIDString(%v) but no error was found", s)
}
}
// Return entire UUID
res, err := GetUUIDFromIDString(validUUID)
if err != nil {
t.Errorf("unexpected error for GetUUIDFromIDString(%v): %v", validUUID, err)
}
if res != validUUID {
t.Errorf("expected result %v for GetUUIDFromIDString(%v) but got %v", validUUID, validUUID, res)
}
// Return UUID from EntryID
for _, s := range validEntryIDs {
res, err := GetUUIDFromIDString(s)
if err != nil {
t.Errorf("unexpected error for GetUUIDFromIDString(%v): %v", s, err)
}
if res != validUUID {
t.Errorf("expected result %v for GetUUIDFromIDString(%v) but got %v", validUUID, s, res)
}
}
}
......@@ -54,6 +54,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/client/timestamp"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/sharding"
"github.com/sigstore/rekor/pkg/signer"
rekord "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
"github.com/sigstore/rekor/pkg/util"
......@@ -255,6 +256,13 @@ func TestGet(t *testing.T) {
out = runCli(t, "search", "--sha", fmt.Sprintf("sha256:%s", hex.EncodeToString(sha[:])))
outputContains(t, out, uuid)
// Exercise GET with the new EntryID (TreeID + UUID)
entryID, err := sharding.CreateEntryIDFromParts("0", uuid)
if err != nil {
t.Error(err)
}
out = runCli(t, "get", "--format=json", "--uuid", entryID.ReturnEntryIDString())
}
func TestSearchNoEntriesRC1(t *testing.T) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment