Skip to content

Commit caf126d

Browse files
authored
Update loginfo API endpoint to return information about inactive shards (#738)
* Update loginfo to return info about inactive shards 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> * Fix typo Signed-off-by: Priya Wadhwa <priya@chainguard.dev> * specify resp code in error Signed-off-by: Priya Wadhwa <priya@chainguard.dev>
1 parent 9b3ded9 commit caf126d

15 files changed

+1010
-23
lines changed

cmd/rekor-cli/app/log_info.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import (
2121
"crypto/x509"
2222
"encoding/hex"
2323
"encoding/pem"
24-
"errors"
2524
"fmt"
2625
"time"
2726

2827
"github.com/go-openapi/swag"
2928
"github.com/google/trillian/merkle/logverifier"
3029
"github.com/google/trillian/merkle/rfc6962"
30+
"github.com/pkg/errors"
3131
rclient "github.com/sigstore/rekor/pkg/generated/client"
32+
"github.com/sigstore/rekor/pkg/generated/models"
3233
"github.com/spf13/cobra"
3334
"github.com/spf13/viper"
3435

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

8283
logInfo := result.GetPayload()
8384

85+
// Verify inactive shards
86+
if err := verifyInactiveTrees(rekorClient, serverURL, logInfo.InactiveShards); err != nil {
87+
return nil, err
88+
}
89+
90+
// Verify the active tree
8491
sth := util.SignedCheckpoint{}
8592
signedTreeHead := swag.StringValue(logInfo.SignedTreeHead)
8693
if err := sth.UnmarshalText([]byte(signedTreeHead)); err != nil {
8794
return nil, err
8895
}
96+
treeID := swag.StringValue(logInfo.TreeID)
8997

90-
if err := verifyTree(rekorClient, signedTreeHead, serverURL); err != nil {
98+
if err := verifyTree(rekorClient, signedTreeHead, serverURL, treeID); err != nil {
9199
return nil, err
92100
}
93101

@@ -101,8 +109,27 @@ var logInfoCmd = &cobra.Command{
101109
}),
102110
}
103111

104-
func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL string) error {
112+
func verifyInactiveTrees(rekorClient *rclient.Rekor, serverURL string, inactiveShards []*models.InactiveShardLogInfo) error {
113+
if inactiveShards == nil {
114+
return nil
115+
}
116+
log.CliLogger.Infof("Validating inactive shards...")
117+
for _, shard := range inactiveShards {
118+
signedTreeHead := swag.StringValue(shard.SignedTreeHead)
119+
treeID := swag.StringValue(shard.TreeID)
120+
if err := verifyTree(rekorClient, signedTreeHead, serverURL, treeID); err != nil {
121+
return errors.Wrapf(err, "verifying inactive shard with ID %s", treeID)
122+
}
123+
}
124+
log.CliLogger.Infof("Successfully validated inactive shards")
125+
return nil
126+
}
127+
128+
func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL, treeID string) error {
105129
oldState := state.Load(serverURL)
130+
if treeID != "" {
131+
oldState = state.Load(treeID)
132+
}
106133
sth := util.SignedCheckpoint{}
107134
if err := sth.UnmarshalText([]byte(signedTreeHead)); err != nil {
108135
return err
@@ -115,19 +142,24 @@ func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL string) er
115142
return errors.New("signature on tree head did not verify")
116143
}
117144

118-
if err := proveConsistency(rekorClient, oldState, sth); err != nil {
145+
if err := proveConsistency(rekorClient, oldState, sth, treeID); err != nil {
119146
return err
120147
}
121148

122149
if viper.GetBool("store_tree_state") {
150+
if treeID != "" {
151+
if err := state.Dump(treeID, &sth); err != nil {
152+
log.CliLogger.Infof("Unable to store previous state: %v", err)
153+
}
154+
}
123155
if err := state.Dump(serverURL, &sth); err != nil {
124156
log.CliLogger.Infof("Unable to store previous state: %v", err)
125157
}
126158
}
127159
return nil
128160
}
129161

130-
func proveConsistency(rekorClient *rclient.Rekor, oldState *util.SignedCheckpoint, sth util.SignedCheckpoint) error {
162+
func proveConsistency(rekorClient *rclient.Rekor, oldState *util.SignedCheckpoint, sth util.SignedCheckpoint, treeID string) error {
131163
if oldState == nil {
132164
log.CliLogger.Infof("No previous log state stored, unable to prove consistency")
133165
return nil
@@ -140,6 +172,7 @@ func proveConsistency(rekorClient *rclient.Rekor, oldState *util.SignedCheckpoin
140172
firstSize := int64(persistedSize)
141173
params.FirstSize = &firstSize
142174
params.LastSize = int64(sth.Size)
175+
params.TreeID = &treeID
143176
proof, err := rekorClient.Tlog.GetLogProof(params)
144177
if err != nil {
145178
return err

cmd/rekor-cli/app/state/state.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727

2828
type persistedState map[string]*util.SignedCheckpoint
2929

30-
func Dump(url string, sth *util.SignedCheckpoint) error {
30+
func Dump(key string, sth *util.SignedCheckpoint) error {
3131
rekorDir, err := getRekorDir()
3232
if err != nil {
3333
return err
@@ -38,7 +38,7 @@ func Dump(url string, sth *util.SignedCheckpoint) error {
3838
if state == nil {
3939
state = make(persistedState)
4040
}
41-
state[url] = sth
41+
state[key] = sth
4242

4343
b, err := json.Marshal(&state)
4444
if err != nil {
@@ -67,9 +67,9 @@ func loadStateFile() persistedState {
6767
return result
6868
}
6969

70-
func Load(url string) *util.SignedCheckpoint {
70+
func Load(key string) *util.SignedCheckpoint {
7171
if state := loadStateFile(); state != nil {
72-
return state[url]
72+
return state[key]
7373
}
7474
return nil
7575
}

openapi.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,35 @@ definitions:
572572
minItems: 1
573573

574574
LogInfo:
575+
type: object
576+
properties:
577+
rootHash:
578+
type: string
579+
description: The current hash value stored at the root of the merkle tree
580+
pattern: '^[0-9a-fA-F]{64}$'
581+
treeSize:
582+
type: integer
583+
description: The current number of nodes in the merkle tree
584+
minimum: 1
585+
signedTreeHead:
586+
type: string
587+
format: signedCheckpoint
588+
description: The current signed tree head
589+
treeID:
590+
type: string
591+
description: The current treeID
592+
pattern: '^[0-9]+$'
593+
inactiveShards:
594+
type: array
595+
items:
596+
$ref: '#/definitions/InactiveShardLogInfo'
597+
598+
required:
599+
- rootHash
600+
- treeSize
601+
- signedTreeHead
602+
- treeID
603+
InactiveShardLogInfo:
575604
type: object
576605
properties:
577606
rootHash:

pkg/api/error.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const (
4949
failedToGenerateTimestampResponse = "Error generating timestamp response"
5050
sthGenerateError = "Error generating signed tree head"
5151
unsupportedPKIFormat = "The PKI format requested is not supported by this server"
52+
unexpectedInactiveShardError = "Unexpected error communicating with inactive shard"
5253
)
5354

5455
func errorMsg(message string, code int) *models.Error {

pkg/api/tlog.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package api
1717

1818
import (
19+
"context"
1920
"encoding/hex"
2021
"fmt"
2122
"net/http"
@@ -39,6 +40,20 @@ import (
3940
func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
4041
tc := NewTrillianClient(params.HTTPRequest.Context())
4142

43+
// for each inactive shard, get the loginfo
44+
var inactiveShards []*models.InactiveShardLogInfo
45+
for _, shard := range tc.ranges.GetRanges() {
46+
if shard.TreeID == tc.ranges.ActiveTreeID() {
47+
break
48+
}
49+
// Get details for this inactive shard
50+
is, err := inactiveShardLogInfo(params.HTTPRequest.Context(), shard.TreeID)
51+
if err != nil {
52+
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("inactive shard error: %w", err), unexpectedInactiveShardError)
53+
}
54+
inactiveShards = append(inactiveShards, is)
55+
}
56+
4257
resp := tc.getLatest(0)
4358
if resp.status != codes.OK {
4459
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.err), trillianCommunicationError)
@@ -80,6 +95,7 @@ func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
8095
TreeSize: &treeSize,
8196
SignedTreeHead: &scString,
8297
TreeID: stringPointer(fmt.Sprintf("%d", tc.logID)),
98+
InactiveShards: inactiveShards,
8399
}
84100

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

137153
return tlog.NewGetLogProofOK().WithPayload(&consistencyProof)
138154
}
155+
156+
func inactiveShardLogInfo(ctx context.Context, tid int64) (*models.InactiveShardLogInfo, error) {
157+
tc := NewTrillianClientFromTreeID(ctx, tid)
158+
resp := tc.getLatest(0)
159+
if resp.status != codes.OK {
160+
return nil, fmt.Errorf("resp code is %d", resp.status)
161+
}
162+
result := resp.getLatestResult
163+
164+
root := &types.LogRootV1{}
165+
if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
166+
return nil, err
167+
}
168+
169+
hashString := hex.EncodeToString(root.RootHash)
170+
treeSize := int64(root.TreeSize)
171+
172+
sth, err := util.CreateSignedCheckpoint(util.Checkpoint{
173+
Origin: "Rekor",
174+
Size: root.TreeSize,
175+
Hash: root.RootHash,
176+
})
177+
if err != nil {
178+
return nil, err
179+
}
180+
sth.SetTimestamp(uint64(time.Now().UnixNano()))
181+
182+
// sign the log root ourselves to get the log root signature
183+
if _, err := sth.Sign(viper.GetString("rekor_server.hostname"), api.signer, options.WithContext(ctx)); err != nil {
184+
return nil, err
185+
}
186+
187+
scBytes, err := sth.SignedNote.MarshalText()
188+
if err != nil {
189+
return nil, err
190+
}
191+
m := models.InactiveShardLogInfo{
192+
RootHash: &hashString,
193+
TreeSize: &treeSize,
194+
TreeID: stringPointer(fmt.Sprintf("%d", tid)),
195+
SignedTreeHead: stringPointer(string(scBytes)),
196+
}
197+
return &m, nil
198+
}

pkg/api/trillian_client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/google/trillian/merkle/rfc6962"
2626
"github.com/pkg/errors"
2727
"github.com/sigstore/rekor/pkg/log"
28+
"github.com/sigstore/rekor/pkg/sharding"
2829

2930
"google.golang.org/grpc/codes"
3031
"google.golang.org/grpc/status"
@@ -37,13 +38,15 @@ import (
3738

3839
type TrillianClient struct {
3940
client trillian.TrillianLogClient
41+
ranges sharding.LogRanges
4042
logID int64
4143
context context.Context
4244
}
4345

4446
func NewTrillianClient(ctx context.Context) TrillianClient {
4547
return TrillianClient{
4648
client: api.logClient,
49+
ranges: api.logRanges,
4750
logID: api.logID,
4851
context: ctx,
4952
}

0 commit comments

Comments
 (0)