From e8d9d0e7e6e310fc247e1dbaed34207588c4d55c Mon Sep 17 00:00:00 2001 From: Priya Wadhwa Date: Thu, 17 Mar 2022 12:32:18 +0000 Subject: [PATCH] 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 --- cmd/rekor-cli/app/log_info.go | 43 ++++- cmd/rekor-cli/app/state/state.go | 8 +- openapi.yaml | 29 ++++ pkg/api/error.go | 1 + pkg/api/tlog.go | 60 +++++++ pkg/api/trillian_client.go | 3 + .../tlog/get_inactive_log_info_parameters.go | 142 ++++++++++++++++ .../tlog/get_inactive_log_info_responses.go | 129 +++++++++++++++ .../models/inactive_shard_log_info.go | 153 ++++++++++++++++++ pkg/generated/models/log_info.go | 65 +++++++- .../operations/tlog/get_inactive_log_info.go | 74 +++++++++ .../tlog/get_inactive_log_info_parameters.go | 62 +++++++ .../tlog/get_inactive_log_info_responses.go | 135 ++++++++++++++++ .../tlog/get_inactive_log_info_urlbuilder.go | 100 ++++++++++++ tests/sharding-e2e-test.sh | 29 ++-- 15 files changed, 1010 insertions(+), 23 deletions(-) create mode 100644 pkg/generated/client/tlog/get_inactive_log_info_parameters.go create mode 100644 pkg/generated/client/tlog/get_inactive_log_info_responses.go create mode 100644 pkg/generated/models/inactive_shard_log_info.go create mode 100644 pkg/generated/restapi/operations/tlog/get_inactive_log_info.go create mode 100644 pkg/generated/restapi/operations/tlog/get_inactive_log_info_parameters.go create mode 100644 pkg/generated/restapi/operations/tlog/get_inactive_log_info_responses.go create mode 100644 pkg/generated/restapi/operations/tlog/get_inactive_log_info_urlbuilder.go diff --git a/cmd/rekor-cli/app/log_info.go b/cmd/rekor-cli/app/log_info.go index 304a29177..d3ab7997d 100644 --- a/cmd/rekor-cli/app/log_info.go +++ b/cmd/rekor-cli/app/log_info.go @@ -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" @@ -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 } @@ -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 @@ -115,11 +142,16 @@ 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) } @@ -127,7 +159,7 @@ func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL string) er 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 @@ -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 diff --git a/cmd/rekor-cli/app/state/state.go b/cmd/rekor-cli/app/state/state.go index bd837fbdf..cfe450801 100644 --- a/cmd/rekor-cli/app/state/state.go +++ b/cmd/rekor-cli/app/state/state.go @@ -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 @@ -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 { @@ -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 } diff --git a/openapi.yaml b/openapi.yaml index 1a4d51260..8508431d1 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -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: diff --git a/pkg/api/error.go b/pkg/api/error.go index 606a8ec31..b2a05c086 100644 --- a/pkg/api/error.go +++ b/pkg/api/error.go @@ -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 { diff --git a/pkg/api/tlog.go b/pkg/api/tlog.go index baffb6a34..7896f73bb 100644 --- a/pkg/api/tlog.go +++ b/pkg/api/tlog.go @@ -16,6 +16,7 @@ package api import ( + "context" "encoding/hex" "fmt" "net/http" @@ -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) @@ -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) @@ -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 +} diff --git a/pkg/api/trillian_client.go b/pkg/api/trillian_client.go index e3a4f7a6b..93610f193 100644 --- a/pkg/api/trillian_client.go +++ b/pkg/api/trillian_client.go @@ -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" @@ -37,6 +38,7 @@ import ( type TrillianClient struct { client trillian.TrillianLogClient + ranges sharding.LogRanges logID int64 context context.Context } @@ -44,6 +46,7 @@ type TrillianClient struct { func NewTrillianClient(ctx context.Context) TrillianClient { return TrillianClient{ client: api.logClient, + ranges: api.logRanges, logID: api.logID, context: ctx, } diff --git a/pkg/generated/client/tlog/get_inactive_log_info_parameters.go b/pkg/generated/client/tlog/get_inactive_log_info_parameters.go new file mode 100644 index 000000000..1e2801abb --- /dev/null +++ b/pkg/generated/client/tlog/get_inactive_log_info_parameters.go @@ -0,0 +1,142 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// 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 tlog + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetInactiveLogInfoParams creates a new GetInactiveLogInfoParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetInactiveLogInfoParams() *GetInactiveLogInfoParams { + return &GetInactiveLogInfoParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetInactiveLogInfoParamsWithTimeout creates a new GetInactiveLogInfoParams object +// with the ability to set a timeout on a request. +func NewGetInactiveLogInfoParamsWithTimeout(timeout time.Duration) *GetInactiveLogInfoParams { + return &GetInactiveLogInfoParams{ + timeout: timeout, + } +} + +// NewGetInactiveLogInfoParamsWithContext creates a new GetInactiveLogInfoParams object +// with the ability to set a context for a request. +func NewGetInactiveLogInfoParamsWithContext(ctx context.Context) *GetInactiveLogInfoParams { + return &GetInactiveLogInfoParams{ + Context: ctx, + } +} + +// NewGetInactiveLogInfoParamsWithHTTPClient creates a new GetInactiveLogInfoParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetInactiveLogInfoParamsWithHTTPClient(client *http.Client) *GetInactiveLogInfoParams { + return &GetInactiveLogInfoParams{ + HTTPClient: client, + } +} + +/* GetInactiveLogInfoParams contains all the parameters to send to the API endpoint + for the get inactive log info operation. + + Typically these are written to a http.Request. +*/ +type GetInactiveLogInfoParams struct { + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get inactive log info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetInactiveLogInfoParams) WithDefaults() *GetInactiveLogInfoParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get inactive log info params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetInactiveLogInfoParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get inactive log info params +func (o *GetInactiveLogInfoParams) WithTimeout(timeout time.Duration) *GetInactiveLogInfoParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get inactive log info params +func (o *GetInactiveLogInfoParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get inactive log info params +func (o *GetInactiveLogInfoParams) WithContext(ctx context.Context) *GetInactiveLogInfoParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get inactive log info params +func (o *GetInactiveLogInfoParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get inactive log info params +func (o *GetInactiveLogInfoParams) WithHTTPClient(client *http.Client) *GetInactiveLogInfoParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get inactive log info params +func (o *GetInactiveLogInfoParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WriteToRequest writes these params to a swagger request +func (o *GetInactiveLogInfoParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/generated/client/tlog/get_inactive_log_info_responses.go b/pkg/generated/client/tlog/get_inactive_log_info_responses.go new file mode 100644 index 000000000..5a7de889b --- /dev/null +++ b/pkg/generated/client/tlog/get_inactive_log_info_responses.go @@ -0,0 +1,129 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// 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 tlog + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/sigstore/rekor/pkg/generated/models" +) + +// GetInactiveLogInfoReader is a Reader for the GetInactiveLogInfo structure. +type GetInactiveLogInfoReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetInactiveLogInfoReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGetInactiveLogInfoOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewGetInactiveLogInfoDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewGetInactiveLogInfoOK creates a GetInactiveLogInfoOK with default headers values +func NewGetInactiveLogInfoOK() *GetInactiveLogInfoOK { + return &GetInactiveLogInfoOK{} +} + +/* GetInactiveLogInfoOK describes a response with status code 200, with default header values. + +A JSON object with the root hash and tree size as properties +*/ +type GetInactiveLogInfoOK struct { + Payload []*models.LogInfo +} + +func (o *GetInactiveLogInfoOK) Error() string { + return fmt.Sprintf("[GET /api/v1/log/inactive][%d] getInactiveLogInfoOK %+v", 200, o.Payload) +} +func (o *GetInactiveLogInfoOK) GetPayload() []*models.LogInfo { + return o.Payload +} + +func (o *GetInactiveLogInfoOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetInactiveLogInfoDefault creates a GetInactiveLogInfoDefault with default headers values +func NewGetInactiveLogInfoDefault(code int) *GetInactiveLogInfoDefault { + return &GetInactiveLogInfoDefault{ + _statusCode: code, + } +} + +/* GetInactiveLogInfoDefault describes a response with status code -1, with default header values. + +There was an internal error in the server while processing the request +*/ +type GetInactiveLogInfoDefault struct { + _statusCode int + + Payload *models.Error +} + +// Code gets the status code for the get inactive log info default response +func (o *GetInactiveLogInfoDefault) Code() int { + return o._statusCode +} + +func (o *GetInactiveLogInfoDefault) Error() string { + return fmt.Sprintf("[GET /api/v1/log/inactive][%d] getInactiveLogInfo default %+v", o._statusCode, o.Payload) +} +func (o *GetInactiveLogInfoDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *GetInactiveLogInfoDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/pkg/generated/models/inactive_shard_log_info.go b/pkg/generated/models/inactive_shard_log_info.go new file mode 100644 index 000000000..c555eb2da --- /dev/null +++ b/pkg/generated/models/inactive_shard_log_info.go @@ -0,0 +1,153 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// 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 models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// InactiveShardLogInfo inactive shard log info +// +// swagger:model InactiveShardLogInfo +type InactiveShardLogInfo struct { + + // The current hash value stored at the root of the merkle tree + // Required: true + // Pattern: ^[0-9a-fA-F]{64}$ + RootHash *string `json:"rootHash"` + + // The current signed tree head + // Required: true + SignedTreeHead *string `json:"signedTreeHead"` + + // The current treeID + // Required: true + // Pattern: ^[0-9]+$ + TreeID *string `json:"treeID"` + + // The current number of nodes in the merkle tree + // Required: true + // Minimum: 1 + TreeSize *int64 `json:"treeSize"` +} + +// Validate validates this inactive shard log info +func (m *InactiveShardLogInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateRootHash(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSignedTreeHead(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTreeID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTreeSize(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *InactiveShardLogInfo) validateRootHash(formats strfmt.Registry) error { + + if err := validate.Required("rootHash", "body", m.RootHash); err != nil { + return err + } + + if err := validate.Pattern("rootHash", "body", *m.RootHash, `^[0-9a-fA-F]{64}$`); err != nil { + return err + } + + return nil +} + +func (m *InactiveShardLogInfo) validateSignedTreeHead(formats strfmt.Registry) error { + + if err := validate.Required("signedTreeHead", "body", m.SignedTreeHead); err != nil { + return err + } + + return nil +} + +func (m *InactiveShardLogInfo) validateTreeID(formats strfmt.Registry) error { + + if err := validate.Required("treeID", "body", m.TreeID); err != nil { + return err + } + + if err := validate.Pattern("treeID", "body", *m.TreeID, `^[0-9]+$`); err != nil { + return err + } + + return nil +} + +func (m *InactiveShardLogInfo) validateTreeSize(formats strfmt.Registry) error { + + if err := validate.Required("treeSize", "body", m.TreeSize); err != nil { + return err + } + + if err := validate.MinimumInt("treeSize", "body", *m.TreeSize, 1, false); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this inactive shard log info based on context it is used +func (m *InactiveShardLogInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *InactiveShardLogInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *InactiveShardLogInfo) UnmarshalBinary(b []byte) error { + var res InactiveShardLogInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/generated/models/log_info.go b/pkg/generated/models/log_info.go index 5af954ea9..33178bc56 100644 --- a/pkg/generated/models/log_info.go +++ b/pkg/generated/models/log_info.go @@ -23,6 +23,7 @@ package models import ( "context" + "strconv" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" @@ -35,6 +36,9 @@ import ( // swagger:model LogInfo type LogInfo struct { + // inactive shards + InactiveShards []*InactiveShardLogInfo `json:"inactiveShards"` + // The current hash value stored at the root of the merkle tree // Required: true // Pattern: ^[0-9a-fA-F]{64}$ @@ -59,6 +63,10 @@ type LogInfo struct { func (m *LogInfo) Validate(formats strfmt.Registry) error { var res []error + if err := m.validateInactiveShards(formats); err != nil { + res = append(res, err) + } + if err := m.validateRootHash(formats); err != nil { res = append(res, err) } @@ -81,6 +89,32 @@ func (m *LogInfo) Validate(formats strfmt.Registry) error { return nil } +func (m *LogInfo) validateInactiveShards(formats strfmt.Registry) error { + if swag.IsZero(m.InactiveShards) { // not required + return nil + } + + for i := 0; i < len(m.InactiveShards); i++ { + if swag.IsZero(m.InactiveShards[i]) { // not required + continue + } + + if m.InactiveShards[i] != nil { + if err := m.InactiveShards[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("inactiveShards" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("inactiveShards" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + func (m *LogInfo) validateRootHash(formats strfmt.Registry) error { if err := validate.Required("rootHash", "body", m.RootHash); err != nil { @@ -129,8 +163,37 @@ func (m *LogInfo) validateTreeSize(formats strfmt.Registry) error { return nil } -// ContextValidate validates this log info based on context it is used +// ContextValidate validate this log info based on the context it is used func (m *LogInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateInactiveShards(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *LogInfo) contextValidateInactiveShards(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.InactiveShards); i++ { + + if m.InactiveShards[i] != nil { + if err := m.InactiveShards[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("inactiveShards" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("inactiveShards" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + return nil } diff --git a/pkg/generated/restapi/operations/tlog/get_inactive_log_info.go b/pkg/generated/restapi/operations/tlog/get_inactive_log_info.go new file mode 100644 index 000000000..f56089f44 --- /dev/null +++ b/pkg/generated/restapi/operations/tlog/get_inactive_log_info.go @@ -0,0 +1,74 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// 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 tlog + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetInactiveLogInfoHandlerFunc turns a function with the right signature into a get inactive log info handler +type GetInactiveLogInfoHandlerFunc func(GetInactiveLogInfoParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetInactiveLogInfoHandlerFunc) Handle(params GetInactiveLogInfoParams) middleware.Responder { + return fn(params) +} + +// GetInactiveLogInfoHandler interface for that can handle valid get inactive log info params +type GetInactiveLogInfoHandler interface { + Handle(GetInactiveLogInfoParams) middleware.Responder +} + +// NewGetInactiveLogInfo creates a new http.Handler for the get inactive log info operation +func NewGetInactiveLogInfo(ctx *middleware.Context, handler GetInactiveLogInfoHandler) *GetInactiveLogInfo { + return &GetInactiveLogInfo{Context: ctx, Handler: handler} +} + +/* GetInactiveLogInfo swagger:route GET /api/v1/log/inactive tlog getInactiveLogInfo + +Get information about the inactive shards of the transparency log + +Returns information about the merkle tree for inactive shards of the log + +*/ +type GetInactiveLogInfo struct { + Context *middleware.Context + Handler GetInactiveLogInfoHandler +} + +func (o *GetInactiveLogInfo) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetInactiveLogInfoParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/generated/restapi/operations/tlog/get_inactive_log_info_parameters.go b/pkg/generated/restapi/operations/tlog/get_inactive_log_info_parameters.go new file mode 100644 index 000000000..95b2f755a --- /dev/null +++ b/pkg/generated/restapi/operations/tlog/get_inactive_log_info_parameters.go @@ -0,0 +1,62 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// 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 tlog + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" +) + +// NewGetInactiveLogInfoParams creates a new GetInactiveLogInfoParams object +// +// There are no default values defined in the spec. +func NewGetInactiveLogInfoParams() GetInactiveLogInfoParams { + + return GetInactiveLogInfoParams{} +} + +// GetInactiveLogInfoParams contains all the bound params for the get inactive log info operation +// typically these are obtained from a http.Request +// +// swagger:parameters getInactiveLogInfo +type GetInactiveLogInfoParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetInactiveLogInfoParams() beforehand. +func (o *GetInactiveLogInfoParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/generated/restapi/operations/tlog/get_inactive_log_info_responses.go b/pkg/generated/restapi/operations/tlog/get_inactive_log_info_responses.go new file mode 100644 index 000000000..cd8f19376 --- /dev/null +++ b/pkg/generated/restapi/operations/tlog/get_inactive_log_info_responses.go @@ -0,0 +1,135 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// 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 tlog + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/sigstore/rekor/pkg/generated/models" +) + +// GetInactiveLogInfoOKCode is the HTTP code returned for type GetInactiveLogInfoOK +const GetInactiveLogInfoOKCode int = 200 + +/*GetInactiveLogInfoOK A JSON object with the root hash and tree size as properties + +swagger:response getInactiveLogInfoOK +*/ +type GetInactiveLogInfoOK struct { + + /* + In: Body + */ + Payload []*models.LogInfo `json:"body,omitempty"` +} + +// NewGetInactiveLogInfoOK creates GetInactiveLogInfoOK with default headers values +func NewGetInactiveLogInfoOK() *GetInactiveLogInfoOK { + + return &GetInactiveLogInfoOK{} +} + +// WithPayload adds the payload to the get inactive log info o k response +func (o *GetInactiveLogInfoOK) WithPayload(payload []*models.LogInfo) *GetInactiveLogInfoOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get inactive log info o k response +func (o *GetInactiveLogInfoOK) SetPayload(payload []*models.LogInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetInactiveLogInfoOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = make([]*models.LogInfo, 0, 50) + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +/*GetInactiveLogInfoDefault There was an internal error in the server while processing the request + +swagger:response getInactiveLogInfoDefault +*/ +type GetInactiveLogInfoDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewGetInactiveLogInfoDefault creates GetInactiveLogInfoDefault with default headers values +func NewGetInactiveLogInfoDefault(code int) *GetInactiveLogInfoDefault { + if code <= 0 { + code = 500 + } + + return &GetInactiveLogInfoDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the get inactive log info default response +func (o *GetInactiveLogInfoDefault) WithStatusCode(code int) *GetInactiveLogInfoDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the get inactive log info default response +func (o *GetInactiveLogInfoDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the get inactive log info default response +func (o *GetInactiveLogInfoDefault) WithPayload(payload *models.Error) *GetInactiveLogInfoDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get inactive log info default response +func (o *GetInactiveLogInfoDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetInactiveLogInfoDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/generated/restapi/operations/tlog/get_inactive_log_info_urlbuilder.go b/pkg/generated/restapi/operations/tlog/get_inactive_log_info_urlbuilder.go new file mode 100644 index 000000000..31de7923e --- /dev/null +++ b/pkg/generated/restapi/operations/tlog/get_inactive_log_info_urlbuilder.go @@ -0,0 +1,100 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// 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 tlog + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// GetInactiveLogInfoURL generates an URL for the get inactive log info operation +type GetInactiveLogInfoURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetInactiveLogInfoURL) WithBasePath(bp string) *GetInactiveLogInfoURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetInactiveLogInfoURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetInactiveLogInfoURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/api/v1/log/inactive" + + _basePath := o._basePath + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetInactiveLogInfoURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetInactiveLogInfoURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetInactiveLogInfoURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetInactiveLogInfoURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetInactiveLogInfoURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetInactiveLogInfoURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/tests/sharding-e2e-test.sh b/tests/sharding-e2e-test.sh index 4701f1bbb..536e53ba8 100755 --- a/tests/sharding-e2e-test.sh +++ b/tests/sharding-e2e-test.sh @@ -65,19 +65,23 @@ done echo -# rekor-cli loginfo should work -$REKOR_CLI loginfo --rekor_server http://localhost:3000 --store_tree_state=false -INITIAL_TREE_ID=$($REKOR_CLI loginfo --rekor_server http://localhost:3000 --format json --store_tree_state=false | jq -r .TreeID) -echo "Initial Tree ID is $INITIAL_TREE_ID" - - # Add some things to the tlog :) -cd tests +pushd tests $REKOR_CLI upload --artifact test_file.txt --signature test_file.sig --public-key test_public_key.key --rekor_server http://localhost:3000 -cd sharding-testdata +popd + +# Make sure we can prove consistency +$REKOR_CLI loginfo --rekor_server http://localhost:3000 + +# Add 2 more entries to the logd +pushd tests/sharding-testdata $REKOR_CLI upload --artifact file1 --signature file1.sig --pki-format=x509 --public-key=ec_public.pem --rekor_server http://localhost:3000 $REKOR_CLI upload --artifact file2 --signature file2.sig --pki-format=x509 --public-key=ec_public.pem --rekor_server http://localhost:3000 -cd ../.. +popd + + +INITIAL_TREE_ID=$($REKOR_CLI loginfo --rekor_server http://localhost:3000 --format json | jq -r .TreeID) +echo "Initial Tree ID is $INITIAL_TREE_ID" # Make sure we have three entries in the log check_log_index 2 @@ -90,7 +94,7 @@ SHARD_TREE_ID=$(createtree --admin_server localhost:8090) echo "the new shard ID is $SHARD_TREE_ID" # Once more -$REKOR_CLI loginfo --rekor_server http://localhost:3000 --store_tree_state=false +$REKOR_CLI loginfo --rekor_server http://localhost:3000 # Spin down the rekor server echo "stopping the rekor server..." @@ -143,11 +147,10 @@ EOF docker-compose -f $COMPOSE_FILE up -d sleep 15 -# TODO: priyawadhwa@ remove --store_tree_state=false once $REKOR_CLI loginfo is aware of shards -$REKOR_CLI loginfo --rekor_server http://localhost:3000 --store_tree_state=false +$REKOR_CLI loginfo --rekor_server http://localhost:3000 # Make sure we are pointing to the new tree now -TREE_ID=$($REKOR_CLI loginfo --rekor_server http://localhost:3000 --format json --store_tree_state=false) +TREE_ID=$($REKOR_CLI loginfo --rekor_server http://localhost:3000 --format json) # Check that the SHARD_TREE_ID is a substring of the `$REKOR_CLI loginfo` output if [[ "$TREE_ID" == *"$SHARD_TREE_ID"* ]]; then echo "Rekor server is now pointing to the new shard"