Skip to content

Commit

Permalink
Update loginfo to return info about inactive shards
Browse files Browse the repository at this point in the history
This also updates `rekor-cli` to verify inactive shards if they exist. It also updates the sharding integration test to run loginfo and store state based on TreeID if available.

Signed-off-by: Priya Wadhwa <priya@chainguard.dev>
  • Loading branch information
priyawadhwa committed Mar 17, 2022
1 parent ebc1d7a commit e8d9d0e
Show file tree
Hide file tree
Showing 15 changed files with 1,010 additions and 23 deletions.
43 changes: 38 additions & 5 deletions cmd/rekor-cli/app/log_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ import (
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"time"

"github.com/go-openapi/swag"
"github.com/google/trillian/merkle/logverifier"
"github.com/google/trillian/merkle/rfc6962"
"github.com/pkg/errors"
rclient "github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/spf13/cobra"
"github.com/spf13/viper"

Expand Down Expand Up @@ -81,13 +82,20 @@ var logInfoCmd = &cobra.Command{

logInfo := result.GetPayload()

// Verify inactive shards
if err := verifyInactiveTrees(rekorClient, serverURL, logInfo.InactiveShards); err != nil {
return nil, err
}

// Verify the active tree
sth := util.SignedCheckpoint{}
signedTreeHead := swag.StringValue(logInfo.SignedTreeHead)
if err := sth.UnmarshalText([]byte(signedTreeHead)); err != nil {
return nil, err
}
treeID := swag.StringValue(logInfo.TreeID)

if err := verifyTree(rekorClient, signedTreeHead, serverURL); err != nil {
if err := verifyTree(rekorClient, signedTreeHead, serverURL, treeID); err != nil {
return nil, err
}

Expand All @@ -101,8 +109,27 @@ var logInfoCmd = &cobra.Command{
}),
}

func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL string) error {
func verifyInactiveTrees(rekorClient *rclient.Rekor, serverURL string, inactiveShards []*models.InactiveShardLogInfo) error {
if inactiveShards == nil {
return nil
}
log.CliLogger.Infof("Validating inactive shards...")
for _, shard := range inactiveShards {
signedTreeHead := swag.StringValue(shard.SignedTreeHead)
treeID := swag.StringValue(shard.TreeID)
if err := verifyTree(rekorClient, signedTreeHead, serverURL, treeID); err != nil {
return errors.Wrapf(err, "verifying inactive shard with ID %s", treeID)
}
}
log.CliLogger.Infof("Successfully validated inactive shards")
return nil
}

func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL, treeID string) error {
oldState := state.Load(serverURL)
if treeID != "" {
oldState = state.Load(treeID)
}
sth := util.SignedCheckpoint{}
if err := sth.UnmarshalText([]byte(signedTreeHead)); err != nil {
return err
Expand All @@ -115,19 +142,24 @@ func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL string) er
return errors.New("signature on tree head did not verify")
}

if err := proveConsistency(rekorClient, oldState, sth); err != nil {
if err := proveConsistency(rekorClient, oldState, sth, treeID); err != nil {
return err
}

if viper.GetBool("store_tree_state") {
if treeID != "" {
if err := state.Dump(treeID, &sth); err != nil {
log.CliLogger.Infof("Unable to store previous state: %v", err)
}
}
if err := state.Dump(serverURL, &sth); err != nil {
log.CliLogger.Infof("Unable to store previous state: %v", err)
}
}
return nil
}

func proveConsistency(rekorClient *rclient.Rekor, oldState *util.SignedCheckpoint, sth util.SignedCheckpoint) error {
func proveConsistency(rekorClient *rclient.Rekor, oldState *util.SignedCheckpoint, sth util.SignedCheckpoint, treeID string) error {
if oldState == nil {
log.CliLogger.Infof("No previous log state stored, unable to prove consistency")
return nil
Expand All @@ -140,6 +172,7 @@ func proveConsistency(rekorClient *rclient.Rekor, oldState *util.SignedCheckpoin
firstSize := int64(persistedSize)
params.FirstSize = &firstSize
params.LastSize = int64(sth.Size)
params.TreeID = &treeID
proof, err := rekorClient.Tlog.GetLogProof(params)
if err != nil {
return err
Expand Down
8 changes: 4 additions & 4 deletions cmd/rekor-cli/app/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

type persistedState map[string]*util.SignedCheckpoint

func Dump(url string, sth *util.SignedCheckpoint) error {
func Dump(key string, sth *util.SignedCheckpoint) error {
rekorDir, err := getRekorDir()
if err != nil {
return err
Expand All @@ -38,7 +38,7 @@ func Dump(url string, sth *util.SignedCheckpoint) error {
if state == nil {
state = make(persistedState)
}
state[url] = sth
state[key] = sth

b, err := json.Marshal(&state)
if err != nil {
Expand Down Expand Up @@ -67,9 +67,9 @@ func loadStateFile() persistedState {
return result
}

func Load(url string) *util.SignedCheckpoint {
func Load(key string) *util.SignedCheckpoint {
if state := loadStateFile(); state != nil {
return state[url]
return state[key]
}
return nil
}
Expand Down
29 changes: 29 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,35 @@ definitions:
minItems: 1

LogInfo:
type: object
properties:
rootHash:
type: string
description: The current hash value stored at the root of the merkle tree
pattern: '^[0-9a-fA-F]{64}$'
treeSize:
type: integer
description: The current number of nodes in the merkle tree
minimum: 1
signedTreeHead:
type: string
format: signedCheckpoint
description: The current signed tree head
treeID:
type: string
description: The current treeID
pattern: '^[0-9]+$'
inactiveShards:
type: array
items:
$ref: '#/definitions/InactiveShardLogInfo'

required:
- rootHash
- treeSize
- signedTreeHead
- treeID
InactiveShardLogInfo:
type: object
properties:
rootHash:
Expand Down
1 change: 1 addition & 0 deletions pkg/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const (
failedToGenerateTimestampResponse = "Error generating timestamp response"
sthGenerateError = "Error generating signed tree head"
unsupportedPKIFormat = "The PKI format requested is not supported by this server"
unexpectedInactiveShardError = "Unexpected error communicating with inactive shard"
)

func errorMsg(message string, code int) *models.Error {
Expand Down
60 changes: 60 additions & 0 deletions pkg/api/tlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package api

import (
"context"
"encoding/hex"
"fmt"
"net/http"
Expand All @@ -39,6 +40,20 @@ import (
func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
tc := NewTrillianClient(params.HTTPRequest.Context())

// for each inactive shard, get the loginfo
var inactiveShards []*models.InactiveShardLogInfo
for _, shard := range tc.ranges.GetRanges() {
if shard.TreeID == tc.ranges.ActiveTreeID() {
break
}
// Get details for this inactive shard
is, err := inactiveShardLogInfo(params.HTTPRequest.Context(), shard.TreeID)
if err != nil {
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("inactive shard error: %w", err), unexpectedInactiveShardError)
}
inactiveShards = append(inactiveShards, is)
}

resp := tc.getLatest(0)
if resp.status != codes.OK {
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.err), trillianCommunicationError)
Expand Down Expand Up @@ -80,6 +95,7 @@ func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
TreeSize: &treeSize,
SignedTreeHead: &scString,
TreeID: stringPointer(fmt.Sprintf("%d", tc.logID)),
InactiveShards: inactiveShards,
}

return tlog.NewGetLogInfoOK().WithPayload(&logInfo)
Expand Down Expand Up @@ -136,3 +152,47 @@ func GetLogProofHandler(params tlog.GetLogProofParams) middleware.Responder {

return tlog.NewGetLogProofOK().WithPayload(&consistencyProof)
}

func inactiveShardLogInfo(ctx context.Context, tid int64) (*models.InactiveShardLogInfo, error) {
tc := NewTrillianClientFromTreeID(ctx, tid)
resp := tc.getLatest(0)
if resp.status != codes.OK {
return nil, fmt.Errorf("resp code is not OK")
}
result := resp.getLatestResult

root := &types.LogRootV1{}
if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
return nil, err
}

hashString := hex.EncodeToString(root.RootHash)
treeSize := int64(root.TreeSize)

sth, err := util.CreateSignedCheckpoint(util.Checkpoint{
Origin: "Rekor",
Size: root.TreeSize,
Hash: root.RootHash,
})
if err != nil {
return nil, err
}
sth.SetTimestamp(uint64(time.Now().UnixNano()))

// sign the log root ourselves to get the log root signature
if _, err := sth.Sign(viper.GetString("rekor_server.hostname"), api.signer, options.WithContext(ctx)); err != nil {
return nil, err
}

scBytes, err := sth.SignedNote.MarshalText()
if err != nil {
return nil, err
}
m := models.InactiveShardLogInfo{
RootHash: &hashString,
TreeSize: &treeSize,
TreeID: stringPointer(fmt.Sprintf("%d", tid)),
SignedTreeHead: stringPointer(string(scBytes)),
}
return &m, nil
}
3 changes: 3 additions & 0 deletions pkg/api/trillian_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/google/trillian/merkle/rfc6962"
"github.com/pkg/errors"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/sharding"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand All @@ -37,13 +38,15 @@ import (

type TrillianClient struct {
client trillian.TrillianLogClient
ranges sharding.LogRanges
logID int64
context context.Context
}

func NewTrillianClient(ctx context.Context) TrillianClient {
return TrillianClient{
client: api.logClient,
ranges: api.logRanges,
logID: api.logID,
context: ctx,
}
Expand Down
Loading

0 comments on commit e8d9d0e

Please sign in to comment.