...

Source file src/github.com/sigstore/rekor/pkg/api/entries.go

Documentation: github.com/sigstore/rekor/pkg/api

     1  //
     2  // Copyright 2021 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package api
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"encoding/hex"
    22  	"errors"
    23  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"strconv"
    27  	"time"
    28  
    29  	"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
    30  	"github.com/go-openapi/runtime"
    31  	"github.com/go-openapi/runtime/middleware"
    32  	"github.com/go-openapi/strfmt"
    33  	"github.com/go-openapi/swag"
    34  	"github.com/google/trillian"
    35  	ttypes "github.com/google/trillian/types"
    36  	"github.com/spf13/viper"
    37  	"github.com/transparency-dev/merkle/rfc6962"
    38  	"google.golang.org/genproto/googleapis/rpc/code"
    39  	"google.golang.org/grpc/codes"
    40  
    41  	"github.com/sigstore/rekor/pkg/events"
    42  	"github.com/sigstore/rekor/pkg/events/newentry"
    43  	"github.com/sigstore/rekor/pkg/generated/models"
    44  	"github.com/sigstore/rekor/pkg/generated/restapi/operations/entries"
    45  	"github.com/sigstore/rekor/pkg/log"
    46  	"github.com/sigstore/rekor/pkg/pubsub"
    47  	"github.com/sigstore/rekor/pkg/sharding"
    48  	"github.com/sigstore/rekor/pkg/tle"
    49  	"github.com/sigstore/rekor/pkg/trillianclient"
    50  	"github.com/sigstore/rekor/pkg/types"
    51  	"github.com/sigstore/rekor/pkg/util"
    52  	"github.com/sigstore/sigstore/pkg/signature"
    53  	"github.com/sigstore/sigstore/pkg/signature/options"
    54  )
    55  
    56  const (
    57  	maxSearchQueries = 10
    58  )
    59  
    60  func signEntry(ctx context.Context, signer signature.Signer, entry models.LogEntryAnon) ([]byte, error) {
    61  	payload, err := entry.MarshalBinary()
    62  	if err != nil {
    63  		return nil, fmt.Errorf("marshalling error: %w", err)
    64  	}
    65  	canonicalized, err := jsoncanonicalizer.Transform(payload)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("canonicalizing error: %w", err)
    68  	}
    69  	signature, err := signer.SignMessage(bytes.NewReader(canonicalized), options.WithContext(ctx))
    70  	if err != nil {
    71  		return nil, fmt.Errorf("signing error: %w", err)
    72  	}
    73  	return signature, nil
    74  }
    75  
    76  // logEntryFromLeaf creates a signed LogEntry struct from trillian structs
    77  func logEntryFromLeaf(ctx context.Context, signer signature.Signer, _ trillianclient.TrillianClient, leaf *trillian.LogLeaf,
    78  	signedLogRoot *trillian.SignedLogRoot, proof *trillian.Proof, tid int64, ranges sharding.LogRanges) (models.LogEntry, error) {
    79  
    80  	log.ContextLogger(ctx).Debugf("log entry from leaf %d", leaf.GetLeafIndex())
    81  	root := &ttypes.LogRootV1{}
    82  	if err := root.UnmarshalBinary(signedLogRoot.LogRoot); err != nil {
    83  		return nil, err
    84  	}
    85  	hashes := []string{}
    86  	for _, hash := range proof.Hashes {
    87  		hashes = append(hashes, hex.EncodeToString(hash))
    88  	}
    89  
    90  	virtualIndex := sharding.VirtualLogIndex(leaf.GetLeafIndex(), tid, ranges)
    91  	logEntryAnon := models.LogEntryAnon{
    92  		LogID:          swag.String(api.pubkeyHash),
    93  		LogIndex:       &virtualIndex,
    94  		Body:           leaf.LeafValue,
    95  		IntegratedTime: swag.Int64(leaf.IntegrateTimestamp.AsTime().Unix()),
    96  	}
    97  
    98  	signature, err := signEntry(ctx, signer, logEntryAnon)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("signing entry error: %w", err)
   101  	}
   102  
   103  	scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tid, root.TreeSize, root.RootHash, api.signer)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	inclusionProof := models.InclusionProof{
   109  		TreeSize:   swag.Int64(int64(root.TreeSize)),
   110  		RootHash:   swag.String(hex.EncodeToString(root.RootHash)),
   111  		LogIndex:   swag.Int64(proof.GetLeafIndex()),
   112  		Hashes:     hashes,
   113  		Checkpoint: stringPointer(string(scBytes)),
   114  	}
   115  
   116  	uuid := hex.EncodeToString(leaf.MerkleLeafHash)
   117  	treeID := fmt.Sprintf("%x", tid)
   118  	entryIDstruct, err := sharding.CreateEntryIDFromParts(treeID, uuid)
   119  	if err != nil {
   120  		return nil, fmt.Errorf("error creating EntryID from active treeID %v and uuid %v: %w", treeID, uuid, err)
   121  	}
   122  	entryID := entryIDstruct.ReturnEntryIDString()
   123  
   124  	if viper.GetBool("enable_attestation_storage") {
   125  		pe, err := models.UnmarshalProposedEntry(bytes.NewReader(leaf.LeafValue), runtime.JSONConsumer())
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  		eimpl, err := types.UnmarshalEntry(pe)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  
   134  		if entryWithAtt, ok := eimpl.(types.EntryWithAttestationImpl); ok {
   135  			var att []byte
   136  			var fetchErr error
   137  			attKey := entryWithAtt.AttestationKey()
   138  			// if we're given a key by the type logic, let's try that first
   139  			if attKey != "" {
   140  				att, fetchErr = attestationStorageClient.FetchAttestation(ctx, attKey)
   141  				if fetchErr != nil {
   142  					log.ContextLogger(ctx).Debugf("error fetching attestation by key, trying by UUID: %s %v", attKey, fetchErr)
   143  				}
   144  			}
   145  			// if looking up by key failed or we weren't able to generate a key, try looking up by uuid
   146  			if attKey == "" || fetchErr != nil {
   147  				att, fetchErr = attestationStorageClient.FetchAttestation(ctx, entryIDstruct.UUID)
   148  				if fetchErr != nil {
   149  					log.ContextLogger(ctx).Debugf("error fetching attestation by uuid: %s %v", entryIDstruct.UUID, fetchErr)
   150  				}
   151  			}
   152  			if fetchErr == nil {
   153  				logEntryAnon.Attestation = &models.LogEntryAnonAttestation{
   154  					Data: att,
   155  				}
   156  			}
   157  		}
   158  	}
   159  
   160  	logEntryAnon.Verification = &models.LogEntryAnonVerification{
   161  		InclusionProof:       &inclusionProof,
   162  		SignedEntryTimestamp: strfmt.Base64(signature),
   163  	}
   164  
   165  	return models.LogEntry{
   166  		entryID: logEntryAnon}, nil
   167  }
   168  
   169  // GetLogEntryAndProofByIndexHandler returns the entry and inclusion proof for a specified log index
   170  func GetLogEntryByIndexHandler(params entries.GetLogEntryByIndexParams) middleware.Responder {
   171  	ctx := params.HTTPRequest.Context()
   172  	logEntry, err := retrieveLogEntryByIndex(ctx, int(params.LogIndex))
   173  	if err != nil {
   174  		if errors.Is(err, ErrNotFound) {
   175  			return handleRekorAPIError(params, http.StatusNotFound, fmt.Errorf("grpc error: %w", err), "")
   176  		}
   177  		return handleRekorAPIError(params, http.StatusInternalServerError, err, err.Error())
   178  	}
   179  	return entries.NewGetLogEntryByIndexOK().WithPayload(logEntry)
   180  }
   181  
   182  func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middleware.Responder) {
   183  	ctx := params.HTTPRequest.Context()
   184  	entry, err := types.CreateVersionedEntry(params.ProposedEntry)
   185  	if err != nil {
   186  		return nil, handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf(validationError, err))
   187  	}
   188  	leaf, err := types.CanonicalizeEntry(ctx, entry)
   189  	if err != nil {
   190  		if _, ok := (err).(types.ValidationError); ok {
   191  			return nil, handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf(validationError, err))
   192  		}
   193  		return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, failedToGenerateCanonicalEntry)
   194  	}
   195  
   196  	tc := trillianclient.NewTrillianClient(ctx, api.logClient, api.logID)
   197  
   198  	resp := tc.AddLeaf(leaf)
   199  	// this represents overall GRPC response state (not the results of insertion into the log)
   200  	if resp.Status != codes.OK {
   201  		return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.Err), trillianUnexpectedResult)
   202  	}
   203  
   204  	// this represents the results of inserting the proposed leaf into the log; status is nil in success path
   205  	insertionStatus := resp.GetAddResult.QueuedLeaf.Status
   206  	if insertionStatus != nil {
   207  		switch insertionStatus.Code {
   208  		case int32(code.Code_OK):
   209  		case int32(code.Code_ALREADY_EXISTS), int32(code.Code_FAILED_PRECONDITION):
   210  			existingUUID := hex.EncodeToString(rfc6962.DefaultHasher.HashLeaf(leaf))
   211  			activeTree := fmt.Sprintf("%x", api.logID)
   212  			entryIDstruct, err := sharding.CreateEntryIDFromParts(activeTree, existingUUID)
   213  			if err != nil {
   214  				err := fmt.Errorf("error creating EntryID from active treeID %v and uuid %v: %w", activeTree, existingUUID, err)
   215  				return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, fmt.Sprintf(validationError, err))
   216  			}
   217  			existingEntryID := entryIDstruct.ReturnEntryIDString()
   218  			err = fmt.Errorf("grpc error: %v", insertionStatus.String())
   219  			return nil, handleRekorAPIError(params, http.StatusConflict, err, fmt.Sprintf(entryAlreadyExists, existingEntryID), "entryURL", getEntryURL(*params.HTTPRequest.URL, existingEntryID))
   220  		default:
   221  			err := fmt.Errorf("grpc error: %v", insertionStatus.String())
   222  			return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, trillianUnexpectedResult)
   223  		}
   224  	}
   225  
   226  	// We made it this far, that means the entry was successfully added.
   227  	metricNewEntries.Inc()
   228  
   229  	queuedLeaf := resp.GetAddResult.QueuedLeaf.Leaf
   230  
   231  	uuid := hex.EncodeToString(queuedLeaf.GetMerkleLeafHash())
   232  	activeTree := fmt.Sprintf("%x", api.logID)
   233  	entryIDstruct, err := sharding.CreateEntryIDFromParts(activeTree, uuid)
   234  	if err != nil {
   235  		err := fmt.Errorf("error creating EntryID from active treeID %v and uuid %v: %w", activeTree, uuid, err)
   236  		return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, fmt.Sprintf(validationError, err))
   237  	}
   238  	entryID := entryIDstruct.ReturnEntryIDString()
   239  
   240  	// The log index should be the virtual log index across all shards
   241  	virtualIndex := sharding.VirtualLogIndex(queuedLeaf.LeafIndex, api.logRanges.ActiveTreeID(), api.logRanges)
   242  	logEntryAnon := models.LogEntryAnon{
   243  		LogID:          swag.String(api.pubkeyHash),
   244  		LogIndex:       swag.Int64(virtualIndex),
   245  		Body:           queuedLeaf.GetLeafValue(),
   246  		IntegratedTime: swag.Int64(queuedLeaf.IntegrateTimestamp.AsTime().Unix()),
   247  	}
   248  
   249  	if indexStorageClient != nil {
   250  		go func() {
   251  			start := time.Now()
   252  			var err error
   253  			defer func() {
   254  				labels := map[string]string{
   255  					"success": strconv.FormatBool(err == nil),
   256  				}
   257  				metricIndexStorageLatency.With(labels).Observe(float64(time.Since(start)))
   258  			}()
   259  			keys, err := entry.IndexKeys()
   260  			if err != nil {
   261  				log.ContextLogger(ctx).Errorf("getting entry index keys: %v", err)
   262  				return
   263  			}
   264  			if err := addToIndex(context.Background(), keys, entryID); err != nil {
   265  				log.ContextLogger(ctx).Errorf("adding keys to index: %v", err)
   266  			}
   267  		}()
   268  	}
   269  
   270  	if viper.GetBool("enable_attestation_storage") {
   271  		if entryWithAtt, ok := entry.(types.EntryWithAttestationImpl); ok {
   272  			attKey, attVal := entryWithAtt.AttestationKeyValue()
   273  			if attVal != nil {
   274  				go func() {
   275  					if err := storeAttestation(context.Background(), attKey, attVal); err != nil {
   276  						// entryIDstruct.UUID
   277  						log.ContextLogger(ctx).Debugf("error storing attestation: %s", err)
   278  					} else {
   279  						log.ContextLogger(ctx).Debugf("stored attestation for uuid %s with filename %s", entryIDstruct.UUID, attKey)
   280  					}
   281  				}()
   282  			} else {
   283  				log.ContextLogger(ctx).Infof("no attestation returned for %s", uuid)
   284  			}
   285  		}
   286  	}
   287  
   288  	signature, err := signEntry(ctx, api.signer, logEntryAnon)
   289  	if err != nil {
   290  		return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("signing entry error: %w", err), signingError)
   291  	}
   292  
   293  	root := &ttypes.LogRootV1{}
   294  	if err := root.UnmarshalBinary(resp.GetLeafAndProofResult.SignedLogRoot.LogRoot); err != nil {
   295  		return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("error unmarshalling log root: %w", err), sthGenerateError)
   296  	}
   297  	hashes := []string{}
   298  	for _, hash := range resp.GetLeafAndProofResult.Proof.Hashes {
   299  		hashes = append(hashes, hex.EncodeToString(hash))
   300  	}
   301  
   302  	scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), api.logID, root.TreeSize, root.RootHash, api.signer)
   303  	if err != nil {
   304  		return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, sthGenerateError)
   305  	}
   306  
   307  	inclusionProof := models.InclusionProof{
   308  		TreeSize:   swag.Int64(int64(root.TreeSize)),
   309  		RootHash:   swag.String(hex.EncodeToString(root.RootHash)),
   310  		LogIndex:   swag.Int64(queuedLeaf.LeafIndex),
   311  		Hashes:     hashes,
   312  		Checkpoint: swag.String(string(scBytes)),
   313  	}
   314  
   315  	logEntryAnon.Verification = &models.LogEntryAnonVerification{
   316  		InclusionProof:       &inclusionProof,
   317  		SignedEntryTimestamp: strfmt.Base64(signature),
   318  	}
   319  
   320  	logEntry := models.LogEntry{
   321  		entryID: logEntryAnon,
   322  	}
   323  
   324  	if api.newEntryPublisher != nil {
   325  		// Publishing notifications should not block the API response.
   326  		go func() {
   327  			verifiers, err := entry.Verifiers()
   328  			if err != nil {
   329  				incPublishEvent(newentry.Name, "", false)
   330  				log.ContextLogger(ctx).Errorf("Could not get verifiers for log entry %s: %v", entryID, err)
   331  				return
   332  			}
   333  			var subjects []string
   334  			for _, v := range verifiers {
   335  				subjects = append(subjects, v.Subjects()...)
   336  			}
   337  
   338  			pbEntry, err := tle.GenerateTransparencyLogEntry(logEntryAnon)
   339  			if err != nil {
   340  				incPublishEvent(newentry.Name, "", false)
   341  				log.ContextLogger(ctx).Error(err)
   342  				return
   343  			}
   344  			event, err := newentry.New(entryID, pbEntry, subjects)
   345  			if err != nil {
   346  				incPublishEvent(newentry.Name, "", false)
   347  				log.ContextLogger(ctx).Error(err)
   348  				return
   349  			}
   350  			if viper.GetBool("rekor_server.publish_events_protobuf") {
   351  				go publishEvent(ctx, api.newEntryPublisher, event, events.ContentTypeProtobuf)
   352  			}
   353  			if viper.GetBool("rekor_server.publish_events_json") {
   354  				go publishEvent(ctx, api.newEntryPublisher, event, events.ContentTypeJSON)
   355  			}
   356  		}()
   357  	}
   358  
   359  	return logEntry, nil
   360  }
   361  
   362  func publishEvent(ctx context.Context, publisher pubsub.Publisher, event *events.Event, contentType events.EventContentType) {
   363  	err := publisher.Publish(context.WithoutCancel(ctx), event, contentType)
   364  	incPublishEvent(event.Type().Name(), contentType, err == nil)
   365  	if err != nil {
   366  		log.ContextLogger(ctx).Error(err)
   367  	}
   368  }
   369  
   370  func incPublishEvent(event string, contentType events.EventContentType, ok bool) {
   371  	status := "SUCCESS"
   372  	if !ok {
   373  		status = "ERROR"
   374  	}
   375  	labels := map[string]string{
   376  		"event":        event,
   377  		"status":       status,
   378  		"content_type": string(contentType),
   379  	}
   380  	metricPublishEvents.With(labels).Inc()
   381  }
   382  
   383  // CreateLogEntryHandler creates new entry into log
   384  func CreateLogEntryHandler(params entries.CreateLogEntryParams) middleware.Responder {
   385  	httpReq := params.HTTPRequest
   386  
   387  	logEntry, err := createLogEntry(params)
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	var uuid string
   393  	for location := range logEntry {
   394  		uuid = location
   395  	}
   396  
   397  	return entries.NewCreateLogEntryCreated().WithPayload(logEntry).WithLocation(getEntryURL(*httpReq.URL, uuid)).WithETag(uuid)
   398  }
   399  
   400  // getEntryURL returns the absolute path to the log entry in a RESTful style
   401  func getEntryURL(locationURL url.URL, uuid string) strfmt.URI {
   402  	// remove API key from output
   403  	query := locationURL.Query()
   404  	query.Del("apiKey")
   405  	locationURL.RawQuery = query.Encode()
   406  	locationURL.Path = fmt.Sprintf("%v/%v", locationURL.Path, uuid)
   407  	return strfmt.URI(locationURL.String())
   408  
   409  }
   410  
   411  // GetLogEntryByUUIDHandler gets log entry and inclusion proof for specified UUID aka merkle leaf hash
   412  func GetLogEntryByUUIDHandler(params entries.GetLogEntryByUUIDParams) middleware.Responder {
   413  	logEntry, err := retrieveLogEntry(params.HTTPRequest.Context(), params.EntryUUID)
   414  	if err != nil {
   415  		if errors.Is(err, ErrNotFound) {
   416  			return handleRekorAPIError(params, http.StatusNotFound, err, "")
   417  		}
   418  		if _, ok := (err).(types.ValidationError); ok {
   419  			return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("incorrectly formatted uuid %s", params.EntryUUID), params.EntryUUID)
   420  		}
   421  		return handleRekorAPIError(params, http.StatusInternalServerError, err, trillianCommunicationError)
   422  	}
   423  	return entries.NewGetLogEntryByUUIDOK().WithPayload(logEntry)
   424  }
   425  
   426  // SearchLogQueryHandler searches log by index, UUID, or proposed entry and returns array of entries found with inclusion proofs
   427  func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Responder {
   428  	httpReqCtx := params.HTTPRequest.Context()
   429  	resultPayload := []models.LogEntry{}
   430  
   431  	totalQueries := len(params.Entry.EntryUUIDs) + len(params.Entry.Entries()) + len(params.Entry.LogIndexes)
   432  	if totalQueries > maxSearchQueries {
   433  		return handleRekorAPIError(params, http.StatusUnprocessableEntity, fmt.Errorf(maxSearchQueryLimit, maxSearchQueries), fmt.Sprintf(maxSearchQueryLimit, maxSearchQueries))
   434  	}
   435  
   436  	if len(params.Entry.EntryUUIDs) > 0 || len(params.Entry.Entries()) > 0 {
   437  		var searchHashes [][]byte
   438  		for _, entryID := range params.Entry.EntryUUIDs {
   439  			// if we got this far, then entryID is either a 64 or 80 character hex string
   440  			err := sharding.ValidateEntryID(entryID)
   441  			if err == nil {
   442  				logEntry, err := retrieveLogEntry(httpReqCtx, entryID)
   443  				if err != nil && !errors.Is(err, ErrNotFound) {
   444  					return handleRekorAPIError(params, http.StatusInternalServerError, err, fmt.Sprintf("error getting log entry for %s", entryID))
   445  				} else if err == nil {
   446  					resultPayload = append(resultPayload, logEntry)
   447  				}
   448  				continue
   449  			} else if len(entryID) == sharding.EntryIDHexStringLen {
   450  				// if ValidateEntryID failed and this is a full length entryID, then we can't search for it
   451  				return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("invalid entryID %s", entryID))
   452  			}
   453  			// At this point, check if we got a uuid instead of an EntryID, so search for the hash later
   454  			uuid := entryID
   455  			if err := sharding.ValidateUUID(uuid); err != nil {
   456  				return handleRekorAPIError(params, http.StatusBadRequest, err, fmt.Sprintf("invalid uuid %s", uuid))
   457  			}
   458  			hash, err := hex.DecodeString(uuid)
   459  			if err != nil {
   460  				return handleRekorAPIError(params, http.StatusBadRequest, err, malformedUUID)
   461  			}
   462  			searchHashes = append(searchHashes, hash)
   463  		}
   464  
   465  		entries := params.Entry.Entries()
   466  		for _, e := range entries {
   467  			entry, err := types.UnmarshalEntry(e)
   468  			if err != nil {
   469  				return handleRekorAPIError(params, http.StatusBadRequest, fmt.Errorf("unmarshalling entry: %w", err), err.Error())
   470  			}
   471  
   472  			leaf, err := types.CanonicalizeEntry(httpReqCtx, entry)
   473  			if err != nil {
   474  				return handleRekorAPIError(params, http.StatusBadRequest, fmt.Errorf("canonicalizing entry: %w", err), err.Error())
   475  			}
   476  			hasher := rfc6962.DefaultHasher
   477  			leafHash := hasher.HashLeaf(leaf)
   478  			searchHashes = append(searchHashes, leafHash)
   479  		}
   480  
   481  		searchByHashResults := make([]map[int64]*trillian.GetEntryAndProofResponse, len(searchHashes))
   482  		for i, hash := range searchHashes {
   483  			var results map[int64]*trillian.GetEntryAndProofResponse
   484  			for _, shard := range api.logRanges.AllShards() {
   485  				tcs := trillianclient.NewTrillianClient(httpReqCtx, api.logClient, shard)
   486  				resp := tcs.GetLeafAndProofByHash(hash)
   487  				switch resp.Status {
   488  				case codes.OK:
   489  					leafResult := resp.GetLeafAndProofResult
   490  					if leafResult != nil && leafResult.Leaf != nil {
   491  						if results == nil {
   492  							results = map[int64]*trillian.GetEntryAndProofResponse{}
   493  						}
   494  						results[shard] = resp.GetLeafAndProofResult
   495  					}
   496  				case codes.NotFound:
   497  					// do nothing here, do not throw 404 error
   498  					continue
   499  				default:
   500  					return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("error getLeafAndProofByHash(%s): code: %v, msg %v", hex.EncodeToString(hash), resp.Status, resp.Err), trillianCommunicationError)
   501  				}
   502  			}
   503  			searchByHashResults[i] = results
   504  		}
   505  
   506  		for _, hashMap := range searchByHashResults {
   507  			for shard, leafResp := range hashMap {
   508  				if leafResp == nil {
   509  					continue
   510  				}
   511  				tcs := trillianclient.NewTrillianClient(httpReqCtx, api.logClient, shard)
   512  				logEntry, err := logEntryFromLeaf(httpReqCtx, api.signer, tcs, leafResp.Leaf, leafResp.SignedLogRoot, leafResp.Proof, shard, api.logRanges)
   513  				if err != nil {
   514  					return handleRekorAPIError(params, http.StatusInternalServerError, err, err.Error())
   515  				}
   516  				resultPayload = append(resultPayload, logEntry)
   517  			}
   518  		}
   519  	}
   520  
   521  	if len(params.Entry.LogIndexes) > 0 {
   522  		for _, logIndex := range params.Entry.LogIndexes {
   523  			logEntry, err := retrieveLogEntryByIndex(httpReqCtx, int(swag.Int64Value(logIndex)))
   524  			if err != nil && !errors.Is(err, ErrNotFound) {
   525  				return handleRekorAPIError(params, http.StatusInternalServerError, err, err.Error())
   526  			} else if err == nil {
   527  				resultPayload = append(resultPayload, logEntry)
   528  			}
   529  		}
   530  	}
   531  
   532  	return entries.NewSearchLogQueryOK().WithPayload(resultPayload)
   533  }
   534  
   535  var ErrNotFound = errors.New("grpc returned 0 leaves with success code")
   536  
   537  func retrieveLogEntryByIndex(ctx context.Context, logIndex int) (models.LogEntry, error) {
   538  	log.ContextLogger(ctx).Infof("Retrieving log entry by index %d", logIndex)
   539  
   540  	tid, resolvedIndex := api.logRanges.ResolveVirtualIndex(logIndex)
   541  	tc := trillianclient.NewTrillianClient(ctx, api.logClient, tid)
   542  	log.ContextLogger(ctx).Debugf("Retrieving resolved index %v from TreeID %v", resolvedIndex, tid)
   543  
   544  	resp := tc.GetLeafAndProofByIndex(resolvedIndex)
   545  	switch resp.Status {
   546  	case codes.OK:
   547  	case codes.NotFound, codes.OutOfRange, codes.InvalidArgument:
   548  		return models.LogEntry{}, ErrNotFound
   549  	default:
   550  		return models.LogEntry{}, fmt.Errorf("grpc err: %w: %s", resp.Err, trillianCommunicationError)
   551  	}
   552  
   553  	result := resp.GetLeafAndProofResult
   554  	leaf := result.Leaf
   555  	if leaf == nil {
   556  		return models.LogEntry{}, ErrNotFound
   557  	}
   558  
   559  	return logEntryFromLeaf(ctx, api.signer, tc, leaf, result.SignedLogRoot, result.Proof, tid, api.logRanges)
   560  }
   561  
   562  // Retrieve a Log Entry
   563  // If a tree ID is specified, look in that tree
   564  // Otherwise, look through all inactive and active shards
   565  func retrieveLogEntry(ctx context.Context, entryUUID string) (models.LogEntry, error) {
   566  	log.ContextLogger(ctx).Debugf("Retrieving log entry %v", entryUUID)
   567  
   568  	uuid, err := sharding.GetUUIDFromIDString(entryUUID)
   569  	if err != nil {
   570  		return nil, sharding.ErrPlainUUID
   571  	}
   572  
   573  	// Get the tree ID and check that shard for the entry
   574  	tid, err := sharding.TreeID(entryUUID)
   575  	if err == nil {
   576  		return retrieveUUIDFromTree(ctx, uuid, tid)
   577  	}
   578  
   579  	// If we got a UUID instead of an EntryID, search all shards
   580  	if errors.Is(err, sharding.ErrPlainUUID) {
   581  		trees := []sharding.LogRange{{TreeID: api.logRanges.ActiveTreeID()}}
   582  		trees = append(trees, api.logRanges.GetInactive()...)
   583  
   584  		for _, t := range trees {
   585  			logEntry, err := retrieveUUIDFromTree(ctx, uuid, t.TreeID)
   586  			if err != nil {
   587  				if errors.Is(err, ErrNotFound) {
   588  					continue
   589  				}
   590  				return nil, err
   591  			}
   592  			return logEntry, nil
   593  		}
   594  		return nil, ErrNotFound
   595  	}
   596  
   597  	return nil, err
   598  }
   599  
   600  func retrieveUUIDFromTree(ctx context.Context, uuid string, tid int64) (models.LogEntry, error) {
   601  	log.ContextLogger(ctx).Debugf("Retrieving log entry %v from tree %d", uuid, tid)
   602  
   603  	hashValue, err := hex.DecodeString(uuid)
   604  	if err != nil {
   605  		return models.LogEntry{}, types.ValidationError(err)
   606  	}
   607  
   608  	tc := trillianclient.NewTrillianClient(ctx, api.logClient, tid)
   609  	log.ContextLogger(ctx).Debugf("Attempting to retrieve UUID %v from TreeID %v", uuid, tid)
   610  
   611  	resp := tc.GetLeafAndProofByHash(hashValue)
   612  	switch resp.Status {
   613  	case codes.OK:
   614  		result := resp.GetLeafAndProofResult
   615  		if resp.Err != nil {
   616  			// this shouldn't be possible since GetLeafAndProofByHash verifies the inclusion proof using a computed leaf hash
   617  			// so this is just a defensive check
   618  			if result.Leaf == nil {
   619  				return models.LogEntry{}, ErrNotFound
   620  			}
   621  			return models.LogEntry{}, err
   622  		}
   623  
   624  		logEntry, err := logEntryFromLeaf(ctx, api.signer, tc, result.Leaf, result.SignedLogRoot, result.Proof, tid, api.logRanges)
   625  		if err != nil {
   626  			return models.LogEntry{}, fmt.Errorf("could not create log entry from leaf: %w", err)
   627  		}
   628  		return logEntry, nil
   629  
   630  	case codes.NotFound:
   631  		return models.LogEntry{}, ErrNotFound
   632  	default:
   633  		log.ContextLogger(ctx).Errorf("Unexpected response code while attempting to retrieve UUID %v from TreeID %v: %v", uuid, tid, resp.Status)
   634  		return models.LogEntry{}, errors.New("unexpected error")
   635  	}
   636  }
   637  
   638  // handlers for APIs that may be disabled in a given instance
   639  
   640  func CreateLogEntryNotImplementedHandler(_ entries.CreateLogEntryParams) middleware.Responder {
   641  	err := &models.Error{
   642  		Code:    http.StatusNotImplemented,
   643  		Message: "Create Entry API not enabled in this Rekor instance",
   644  	}
   645  
   646  	return entries.NewCreateLogEntryDefault(http.StatusNotImplemented).WithPayload(err)
   647  }
   648  
   649  func GetLogEntryByIndexNotImplementedHandler(_ entries.GetLogEntryByIndexParams) middleware.Responder {
   650  	err := &models.Error{
   651  		Code:    http.StatusNotImplemented,
   652  		Message: "Get Log Entry by Index API not enabled in this Rekor instance",
   653  	}
   654  
   655  	return entries.NewGetLogEntryByIndexDefault(http.StatusNotImplemented).WithPayload(err)
   656  }
   657  
   658  func GetLogEntryByUUIDNotImplementedHandler(_ entries.GetLogEntryByUUIDParams) middleware.Responder {
   659  	err := &models.Error{
   660  		Code:    http.StatusNotImplemented,
   661  		Message: "Get Log Entry by UUID API not enabled in this Rekor instance",
   662  	}
   663  
   664  	return entries.NewGetLogEntryByUUIDDefault(http.StatusNotImplemented).WithPayload(err)
   665  }
   666  
   667  func SearchLogQueryNotImplementedHandler(_ entries.SearchLogQueryParams) middleware.Responder {
   668  	err := &models.Error{
   669  		Code:    http.StatusNotImplemented,
   670  		Message: "Search Log Query API not enabled in this Rekor instance",
   671  	}
   672  
   673  	return entries.NewSearchLogQueryDefault(http.StatusNotImplemented).WithPayload(err)
   674  }
   675  

View as plain text