Skip to content

Commit cce6cab

Browse files
authored
Update rekor REST API to match Trillian semantics (sigstore#250)
This patch removes the /api/v1/log/entries/{uuid}/proof endpoint. If you have the UUID (aka the leaf Merkle hash), you likely want proof that the content represented by that hash is included in the log. There's no need for a separate /proof endpoint to deliver the same content. This commit also ensures that the getLogEntryByIndex and getLogEntryByUUID endpoints return an inclusion proof as part of their response content. The search endpoint also now returns the inclusion proof of all entries returned from the query. With this patch, Rekor no longer uses the deprecated `GetLeavesByHash` Trillian API. Fixes sigstore#229 Signed-off-by: Bob Callaway <bob.callaway@gmail.com>
1 parent 90739a9 commit cce6cab

29 files changed

+351
-1178
lines changed

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ ADD go.mod go.sum $APP_ROOT/src/
77
RUN go mod download
88

99
# Add source code
10-
ADD ./ $APP_ROOT/src/
10+
ADD ./cmd/ $APP_ROOT/src/cmd/
11+
ADD ./pkg/ $APP_ROOT/src/pkg/
1112

1213
RUN go build ./cmd/rekor-server
1314
RUN CGO_ENABLED=0 go build -gcflags "all=-N -l" -o rekor-server_debug ./cmd/rekor-server

Makefile

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
.PHONY: all test clean lint gosec
1+
.PHONY: all test clean clean-gen lint gosec
22

3-
all: cli server
3+
all: rekor-cli rekor-server
44

55
GENSRC = pkg/generated/client/%.go pkg/generated/models/%.go pkg/generated/restapi/%.go
66
OPENAPIDEPS = openapi.yaml $(shell find pkg/types -iname "*.json")
@@ -20,17 +20,20 @@ lint:
2020
gosec:
2121
$(GOBIN)/gosec ./...
2222

23-
cli: $(SRCS)
23+
rekor-cli: $(SRCS)
2424
go build ./cmd/rekor-cli
2525

26-
server: $(SRCS)
26+
rekor-server: $(SRCS)
2727
go build ./cmd/rekor-server
2828

2929
test:
3030
go test ./...
3131

3232
clean:
33-
rm -rf cli server
33+
rm -rf rekor-cli rekor-server
34+
35+
clean-gen: clean
36+
rm -rf $(shell find pkg/generated -iname "*.go"|grep -v pkg/generated/restapi/configure_rekor_server.go)
3437

3538
up:
3639
docker-compose -f docker-compose.yml build

cmd/rekor-cli/app/log_info.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ var logInfoCmd = &cobra.Command{
160160
log.CliLogger.Infof("Consistency proof valid!")
161161
} else if persistedSize == lr.TreeSize {
162162
if !bytes.Equal(oldState.RootHash, lr.RootHash) {
163-
return nil, errors.New("Root hash returned from server does not match previously persisted state")
163+
return nil, errors.New("root hash returned from server does not match previously persisted state")
164164
}
165165
log.CliLogger.Infof("Persisted log state matches the current state of the log")
166166
} else if persistedSize > lr.TreeSize {
167-
return nil, fmt.Errorf("Current size of tree reported from server %d is less than previously persisted state %d", lr.TreeSize, persistedSize)
167+
return nil, fmt.Errorf("current size of tree reported from server %d is less than previously persisted state %d", lr.TreeSize, persistedSize)
168168
}
169169
} else {
170170
log.CliLogger.Infof("No previous log state stored, unable to prove consistency")

cmd/rekor-cli/app/log_proof.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ var logProofCmd = &cobra.Command{
5353
PreRunE: func(cmd *cobra.Command, args []string) error {
5454
// these are bound here so that they are not overwritten by other commands
5555
if err := viper.BindPFlags(cmd.Flags()); err != nil {
56-
return fmt.Errorf("Error initializing cmd line args: %s", err)
56+
return fmt.Errorf("error initializing cmd line args: %s", err)
5757
}
5858
if viper.GetUint64("first-size") == 0 {
5959
return errors.New("first-size must be > 0")

cmd/rekor-cli/app/verify.go

Lines changed: 50 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ var verifyCmd = &cobra.Command{
7575
PreRunE: func(cmd *cobra.Command, args []string) error {
7676
// these are bound here so that they are not overwritten by other commands
7777
if err := viper.BindPFlags(cmd.Flags()); err != nil {
78-
return fmt.Errorf("Error initializing cmd line args: %s", err)
78+
return fmt.Errorf("error initializing cmd line args: %s", err)
7979
}
8080
if err := validateArtifactPFlags(true, true); err != nil {
8181
_ = cmd.Help()
@@ -89,86 +89,76 @@ var verifyCmd = &cobra.Command{
8989
return nil, err
9090
}
9191

92-
params := entries.NewGetLogEntryProofParams()
93-
params.EntryUUID = viper.GetString("uuid")
94-
if params.EntryUUID == "" {
95-
// without the UUID, we need to search for it
96-
searchParams := entries.NewSearchLogQueryParams()
97-
searchLogQuery := models.SearchLogQuery{}
92+
searchParams := entries.NewSearchLogQueryParams()
93+
searchLogQuery := models.SearchLogQuery{}
9894

99-
logIndex := viper.GetString("log-index")
100-
if logIndex != "" {
101-
logIndexInt, err := strconv.ParseInt(logIndex, 10, 0)
95+
uuid := viper.GetString("uuid")
96+
logIndex := viper.GetString("log-index")
97+
98+
if uuid != "" {
99+
searchLogQuery.EntryUUIDs = append(searchLogQuery.EntryUUIDs, uuid)
100+
} else if logIndex != "" {
101+
logIndexInt, err := strconv.ParseInt(logIndex, 10, 0)
102+
if err != nil {
103+
return nil, fmt.Errorf("error parsing --log-index: %w", err)
104+
}
105+
searchLogQuery.LogIndexes = []*int64{&logIndexInt}
106+
} else {
107+
var entry models.ProposedEntry
108+
switch viper.GetString("type") {
109+
case "rekord":
110+
entry, err = CreateRekordFromPFlags()
102111
if err != nil {
103-
return nil, fmt.Errorf("error parsing --log-index: %w", err)
112+
return nil, err
104113
}
105-
searchLogQuery.LogIndexes = []*int64{&logIndexInt}
106-
} else {
107-
var entry models.ProposedEntry
108-
switch viper.GetString("type") {
109-
case "rekord":
110-
entry, err = CreateRekordFromPFlags()
111-
if err != nil {
112-
return nil, err
113-
}
114-
case "rpm":
115-
entry, err = CreateRpmFromPFlags()
116-
if err != nil {
117-
return nil, err
118-
}
119-
default:
120-
return nil, errors.New("invalid type specified")
114+
case "rpm":
115+
entry, err = CreateRpmFromPFlags()
116+
if err != nil {
117+
return nil, err
121118
}
122-
123-
entries := []models.ProposedEntry{entry}
124-
searchLogQuery.SetEntries(entries)
125-
}
126-
searchParams.SetEntry(&searchLogQuery)
127-
128-
resp, err := rekorClient.Entries.SearchLogQuery(searchParams)
129-
if err != nil {
130-
return nil, err
119+
default:
120+
return nil, errors.New("invalid type specified")
131121
}
132122

133-
if len(resp.Payload) == 0 {
134-
return nil, fmt.Errorf("entry in log cannot be located")
135-
} else if len(resp.Payload) > 1 {
136-
return nil, fmt.Errorf("multiple entries returned; this should not happen")
137-
}
138-
logEntry := resp.Payload[0]
139-
if len(logEntry) != 1 {
140-
return nil, errors.New("UUID value can not be extracted")
141-
}
142-
for k := range logEntry {
143-
params.EntryUUID = k
144-
}
123+
entries := []models.ProposedEntry{entry}
124+
searchLogQuery.SetEntries(entries)
145125
}
126+
searchParams.SetEntry(&searchLogQuery)
146127

147-
resp, err := rekorClient.Entries.GetLogEntryProof(params)
128+
resp, err := rekorClient.Entries.SearchLogQuery(searchParams)
148129
if err != nil {
149130
return nil, err
150131
}
151132

152-
o := &verifyCmdOutput{
153-
RootHash: *resp.Payload.RootHash,
154-
EntryUUID: params.EntryUUID,
155-
Index: *resp.Payload.LogIndex,
156-
Size: *resp.Payload.TreeSize,
157-
Hashes: resp.Payload.Hashes,
133+
if len(resp.Payload) == 0 {
134+
return nil, fmt.Errorf("entry in log cannot be located")
135+
} else if len(resp.Payload) > 1 {
136+
return nil, fmt.Errorf("multiple entries returned; this should not happen")
137+
}
138+
logEntry := resp.Payload[0]
139+
140+
var o *verifyCmdOutput
141+
for k, v := range logEntry {
142+
o = &verifyCmdOutput{
143+
RootHash: *v.InclusionProof.RootHash,
144+
EntryUUID: k,
145+
Index: *v.LogIndex,
146+
Size: *v.InclusionProof.TreeSize,
147+
Hashes: v.InclusionProof.Hashes,
148+
}
158149
}
159150

160151
hashes := [][]byte{}
161-
for _, h := range resp.Payload.Hashes {
152+
for _, h := range o.Hashes {
162153
hb, _ := hex.DecodeString(h)
163154
hashes = append(hashes, hb)
164155
}
165156

166-
rootHash, _ := hex.DecodeString(*resp.Payload.RootHash)
167-
leafHash, _ := hex.DecodeString(params.EntryUUID)
157+
rootHash, _ := hex.DecodeString(o.RootHash)
158+
leafHash, _ := hex.DecodeString(o.EntryUUID)
168159

169160
v := logverifier.New(rfc6962.DefaultHasher)
170-
if err := v.VerifyInclusionProof(*resp.Payload.LogIndex, *resp.Payload.TreeSize,
171-
hashes, rootHash, leafHash); err != nil {
161+
if err := v.VerifyInclusionProof(o.Index, o.Size, hashes, rootHash, leafHash); err != nil {
172162
return nil, err
173163
}
174164
return o, err

openapi.yaml

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ paths:
4141
$ref: '#/responses/BadContent'
4242
default:
4343
$ref: '#/responses/InternalServerError'
44+
4445
/api/v1/log:
4546
get:
4647
summary: Get information about the current state of the transparency log
@@ -140,7 +141,7 @@ paths:
140141
default:
141142
$ref: '#/responses/InternalServerError'
142143
get:
143-
summary: Retrieves an entry from the transparency log (if it exists) by index
144+
summary: Retrieves an entry and inclusion proof from the transparency log (if it exists) by index
144145
operationId: getLogEntryByIndex
145146
tags:
146147
- entries
@@ -153,7 +154,7 @@ paths:
153154
description: specifies the index of the entry in the transparency log to be retrieved
154155
responses:
155156
200:
156-
description: the entry in the transparency log requested
157+
description: the entry in the transparency log requested along with an inclusion proof
157158
schema:
158159
$ref: '#/definitions/LogEntry'
159160
404:
@@ -163,32 +164,9 @@ paths:
163164

164165
/api/v1/log/entries/{entryUUID}:
165166
get:
166-
summary: Retrieves an entry from the transparency log (if it exists) by UUID
167+
summary: Get log entry and information required to generate an inclusion proof for the entry in the transparency log
168+
description: Returns the entry, root hash, tree size, and a list of hashes that can be used to calculate proof of an entry being included in the transparency log
167169
operationId: getLogEntryByUUID
168-
tags:
169-
- entries
170-
parameters:
171-
- in: path
172-
name: entryUUID
173-
type: string
174-
required: true
175-
pattern: '^[0-9a-fA-F]{64}$'
176-
description: the UUID of the entry to be retrieved from the log. The UUID is also the merkle tree hash of the entry.
177-
responses:
178-
200:
179-
description: the entry in the transparency log requested
180-
schema:
181-
$ref: '#/definitions/LogEntry'
182-
404:
183-
$ref: '#/responses/NotFound'
184-
default:
185-
$ref: '#/responses/InternalServerError'
186-
187-
/api/v1/log/entries/{entryUUID}/proof:
188-
get:
189-
summary: Get information required to generate an inclusion proof for a specified entry in the transparency log
190-
description: Returns root hash, tree size, and a list of hashes that can be used to calculate proof of an entry being included in the transparency log
191-
operationId: getLogEntryProof
192170
tags:
193171
- entries
194172
parameters:
@@ -202,7 +180,7 @@ paths:
202180
200:
203181
description: Information needed for a client to compute the inclusion proof
204182
schema:
205-
$ref: '#/definitions/InclusionProof'
183+
$ref: '#/definitions/LogEntry'
206184
404:
207185
$ref: '#/responses/NotFound'
208186
default:
@@ -289,7 +267,10 @@ definitions:
289267
additionalProperties: true
290268
integratedTime:
291269
type: integer
270+
inclusionProof:
271+
$ref: '#/definitions/InclusionProof'
292272
required:
273+
- "logIndex"
293274
- "body"
294275

295276
SearchIndex:

0 commit comments

Comments
 (0)