...

Source file src/github.com/sigstore/rekor/pkg/api/tlog.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  	"context"
    20  	"encoding/hex"
    21  	"fmt"
    22  	"net/http"
    23  	"strconv"
    24  
    25  	"github.com/go-openapi/runtime/middleware"
    26  	"github.com/go-openapi/swag"
    27  	"github.com/google/trillian/types"
    28  	"github.com/spf13/viper"
    29  	"google.golang.org/grpc/codes"
    30  
    31  	"github.com/sigstore/rekor/pkg/generated/models"
    32  	"github.com/sigstore/rekor/pkg/generated/restapi/operations/tlog"
    33  	"github.com/sigstore/rekor/pkg/log"
    34  	"github.com/sigstore/rekor/pkg/trillianclient"
    35  	"github.com/sigstore/rekor/pkg/util"
    36  )
    37  
    38  // GetLogInfoHandler returns the current size of the tree and the STH
    39  func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
    40  	tc := trillianclient.NewTrillianClient(params.HTTPRequest.Context(), api.logClient, api.logID)
    41  
    42  	// for each inactive shard, get the loginfo
    43  	var inactiveShards []*models.InactiveShardLogInfo
    44  	for _, shard := range api.logRanges.GetInactive() {
    45  		if shard.TreeID == api.logRanges.ActiveTreeID() {
    46  			break
    47  		}
    48  		// Get details for this inactive shard
    49  		is, err := inactiveShardLogInfo(params.HTTPRequest.Context(), shard.TreeID)
    50  		if err != nil {
    51  			return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("inactive shard error: %w", err), unexpectedInactiveShardError)
    52  		}
    53  		inactiveShards = append(inactiveShards, is)
    54  	}
    55  
    56  	if swag.BoolValue(params.Stable) && redisClient != nil {
    57  		// key is treeID/latest
    58  		key := fmt.Sprintf("%d/latest", api.logRanges.ActiveTreeID())
    59  		redisResult, err := redisClient.Get(params.HTTPRequest.Context(), key).Result()
    60  		if err != nil {
    61  			return handleRekorAPIError(params, http.StatusInternalServerError,
    62  				fmt.Errorf("error getting checkpoint from redis: %w", err), "error getting checkpoint from redis")
    63  		}
    64  		// should not occur, a checkpoint should always be present
    65  		if redisResult == "" {
    66  			return handleRekorAPIError(params, http.StatusInternalServerError,
    67  				fmt.Errorf("no checkpoint found in redis: %w", err), "no checkpoint found in redis")
    68  		}
    69  		decoded, err := hex.DecodeString(redisResult)
    70  		if err != nil {
    71  			return handleRekorAPIError(params, http.StatusInternalServerError,
    72  				fmt.Errorf("error decoding checkpoint from redis: %w", err), "error decoding checkpoint from redis")
    73  		}
    74  		checkpoint := util.SignedCheckpoint{}
    75  		if err := checkpoint.UnmarshalText(decoded); err != nil {
    76  			return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("invalid checkpoint: %w", err), "invalid checkpoint")
    77  		}
    78  		logInfo := models.LogInfo{
    79  			RootHash:       stringPointer(hex.EncodeToString(checkpoint.Hash)),
    80  			TreeSize:       swag.Int64(int64(checkpoint.Size)),
    81  			SignedTreeHead: stringPointer(string(decoded)),
    82  			TreeID:         stringPointer(fmt.Sprintf("%d", api.logID)),
    83  			InactiveShards: inactiveShards,
    84  		}
    85  		return tlog.NewGetLogInfoOK().WithPayload(&logInfo)
    86  	}
    87  
    88  	resp := tc.GetLatest(0)
    89  	if resp.Status != codes.OK {
    90  		return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.Err), trillianCommunicationError)
    91  	}
    92  	result := resp.GetLatestResult
    93  
    94  	root := &types.LogRootV1{}
    95  	if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
    96  		return handleRekorAPIError(params, http.StatusInternalServerError, err, trillianUnexpectedResult)
    97  	}
    98  
    99  	hashString := hex.EncodeToString(root.RootHash)
   100  	treeSize := int64(root.TreeSize)
   101  
   102  	scBytes, err := util.CreateAndSignCheckpoint(params.HTTPRequest.Context(),
   103  		viper.GetString("rekor_server.hostname"), api.logRanges.ActiveTreeID(), root.TreeSize, root.RootHash, api.signer)
   104  	if err != nil {
   105  		return handleRekorAPIError(params, http.StatusInternalServerError, err, sthGenerateError)
   106  	}
   107  
   108  	logInfo := models.LogInfo{
   109  		RootHash:       &hashString,
   110  		TreeSize:       &treeSize,
   111  		SignedTreeHead: stringPointer(string(scBytes)),
   112  		TreeID:         stringPointer(fmt.Sprintf("%d", api.logID)),
   113  		InactiveShards: inactiveShards,
   114  	}
   115  
   116  	return tlog.NewGetLogInfoOK().WithPayload(&logInfo)
   117  }
   118  
   119  func stringPointer(s string) *string {
   120  	return &s
   121  }
   122  
   123  // GetLogProofHandler returns information required to compute a consistency proof between two snapshots of log
   124  func GetLogProofHandler(params tlog.GetLogProofParams) middleware.Responder {
   125  	if *params.FirstSize > params.LastSize {
   126  		return handleRekorAPIError(params, http.StatusBadRequest, nil, fmt.Sprintf(firstSizeLessThanLastSize, *params.FirstSize, params.LastSize))
   127  	}
   128  	tc := trillianclient.NewTrillianClient(params.HTTPRequest.Context(), api.logClient, api.logID)
   129  	if treeID := swag.StringValue(params.TreeID); treeID != "" {
   130  		id, err := strconv.Atoi(treeID)
   131  		if err != nil {
   132  			log.Logger.Infof("Unable to convert %s to string, skipping initializing client with Tree ID: %v", treeID, err)
   133  		} else {
   134  			tc = trillianclient.NewTrillianClient(params.HTTPRequest.Context(), api.logClient, int64(id))
   135  		}
   136  	}
   137  
   138  	resp := tc.GetConsistencyProof(*params.FirstSize, params.LastSize)
   139  	if resp.Status != codes.OK {
   140  		return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %w", resp.Err), trillianCommunicationError)
   141  	}
   142  	result := resp.GetConsistencyProofResult
   143  
   144  	var root types.LogRootV1
   145  	if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
   146  		return handleRekorAPIError(params, http.StatusInternalServerError, err, trillianUnexpectedResult)
   147  	}
   148  
   149  	hashString := hex.EncodeToString(root.RootHash)
   150  	proofHashes := []string{}
   151  
   152  	if proof := result.GetProof(); proof != nil {
   153  		for _, hash := range proof.Hashes {
   154  			proofHashes = append(proofHashes, hex.EncodeToString(hash))
   155  		}
   156  	} else {
   157  		// The proof field may be empty if the requested tree_size was larger than that available at the server
   158  		// (e.g. because there is skew between server instances, and an earlier client request was processed by
   159  		// a more up-to-date instance). root.TreeSize is the maximum size currently observed
   160  		err := fmt.Errorf(lastSizeGreaterThanKnown, params.LastSize, root.TreeSize)
   161  		return handleRekorAPIError(params, http.StatusBadRequest, err, err.Error())
   162  	}
   163  
   164  	consistencyProof := models.ConsistencyProof{
   165  		RootHash: &hashString,
   166  		Hashes:   proofHashes,
   167  	}
   168  
   169  	return tlog.NewGetLogProofOK().WithPayload(&consistencyProof)
   170  }
   171  
   172  func inactiveShardLogInfo(ctx context.Context, tid int64) (*models.InactiveShardLogInfo, error) {
   173  	tc := trillianclient.NewTrillianClient(ctx, api.logClient, tid)
   174  	resp := tc.GetLatest(0)
   175  	if resp.Status != codes.OK {
   176  		return nil, fmt.Errorf("resp code is %d", resp.Status)
   177  	}
   178  	result := resp.GetLatestResult
   179  
   180  	root := &types.LogRootV1{}
   181  	if err := root.UnmarshalBinary(result.SignedLogRoot.LogRoot); err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	hashString := hex.EncodeToString(root.RootHash)
   186  	treeSize := int64(root.TreeSize)
   187  
   188  	scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tid, root.TreeSize, root.RootHash, api.signer)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	m := models.InactiveShardLogInfo{
   194  		RootHash:       &hashString,
   195  		TreeSize:       &treeSize,
   196  		TreeID:         stringPointer(fmt.Sprintf("%d", tid)),
   197  		SignedTreeHead: stringPointer(string(scBytes)),
   198  	}
   199  	return &m, nil
   200  }
   201  
   202  // handlers for APIs that may be disabled in a given instance
   203  
   204  func GetLogInfoNotImplementedHandler(_ tlog.GetLogInfoParams) middleware.Responder {
   205  	err := &models.Error{
   206  		Code:    http.StatusNotImplemented,
   207  		Message: "Get Log Info API not enabled in this Rekor instance",
   208  	}
   209  
   210  	return tlog.NewGetLogInfoDefault(http.StatusNotImplemented).WithPayload(err)
   211  }
   212  
   213  func GetLogProofNotImplementedHandler(_ tlog.GetLogProofParams) middleware.Responder {
   214  	err := &models.Error{
   215  		Code:    http.StatusNotImplemented,
   216  		Message: "Get Log Proof API not enabled in this Rekor instance",
   217  	}
   218  
   219  	return tlog.NewGetLogProofDefault(http.StatusNotImplemented).WithPayload(err)
   220  }
   221  

View as plain text