...

Source file src/github.com/sigstore/rekor/cmd/rekor-cli/app/get.go

Documentation: github.com/sigstore/rekor/cmd/rekor-cli/app

     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 app
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"encoding/base64"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"strconv"
    26  	"time"
    27  
    28  	"github.com/go-openapi/runtime"
    29  	"github.com/spf13/cobra"
    30  	"github.com/spf13/viper"
    31  
    32  	"github.com/sigstore/rekor/cmd/rekor-cli/app/format"
    33  	"github.com/sigstore/rekor/pkg/client"
    34  	"github.com/sigstore/rekor/pkg/generated/client/entries"
    35  	"github.com/sigstore/rekor/pkg/generated/models"
    36  	"github.com/sigstore/rekor/pkg/log"
    37  	"github.com/sigstore/rekor/pkg/sharding"
    38  	"github.com/sigstore/rekor/pkg/tle"
    39  	"github.com/sigstore/rekor/pkg/types"
    40  	"github.com/sigstore/rekor/pkg/verify"
    41  )
    42  
    43  type getCmdOutput struct {
    44  	Attestation     string
    45  	AttestationType string
    46  	Body            interface{}
    47  	LogIndex        int
    48  	IntegratedTime  int64
    49  	UUID            string
    50  	LogID           string
    51  }
    52  
    53  func (g *getCmdOutput) String() string {
    54  	s := fmt.Sprintf("LogID: %v\n", g.LogID)
    55  
    56  	if g.Attestation != "" {
    57  		s += fmt.Sprintf("Attestation: %s\n", g.Attestation)
    58  	}
    59  
    60  	s += fmt.Sprintf("Index: %d\n", g.LogIndex)
    61  	dt := time.Unix(g.IntegratedTime, 0).UTC().Format(time.RFC3339)
    62  	s += fmt.Sprintf("IntegratedTime: %s\n", dt)
    63  	s += fmt.Sprintf("UUID: %s\n", g.UUID)
    64  	var b bytes.Buffer
    65  	e := json.NewEncoder(&b)
    66  	e.SetIndent("", "  ")
    67  	_ = e.Encode(g.Body)
    68  	s += fmt.Sprintf("Body: %s\n", b.Bytes())
    69  	return s
    70  }
    71  
    72  // getCmd represents the get command
    73  var getCmd = &cobra.Command{
    74  	Use:   "get",
    75  	Short: "Rekor get command",
    76  	Long:  `Get information regarding entries in the transparency log`,
    77  	PreRun: func(cmd *cobra.Command, _ []string) {
    78  		// these are bound here so that they are not overwritten by other commands
    79  		if err := viper.BindPFlags(cmd.Flags()); err != nil {
    80  			log.CliLogger.Fatalf("Error initializing cmd line args: %w", err)
    81  		}
    82  	},
    83  	Run: format.WrapCmd(func(_ []string) (interface{}, error) {
    84  		ctx := context.Background()
    85  		rekorClient, err := client.GetRekorClient(viper.GetString("rekor_server"), client.WithUserAgent(UserAgent()), client.WithRetryCount(viper.GetUint("retry")), client.WithLogger(log.CliLogger))
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  
    90  		logIndex := viper.GetString("log-index")
    91  		uuid := viper.GetString("uuid")
    92  		if logIndex == "" && uuid == "" {
    93  			return nil, errors.New("either --uuid or --log-index must be specified")
    94  		}
    95  		// retrieve rekor pubkey for verification
    96  		verifier, err := loadVerifier(rekorClient)
    97  		if err != nil {
    98  			return nil, fmt.Errorf("retrieving rekor public key")
    99  		}
   100  
   101  		if logIndex != "" {
   102  			params := entries.NewGetLogEntryByIndexParams()
   103  			params.SetTimeout(viper.GetDuration("timeout"))
   104  			logIndexInt, err := strconv.ParseInt(logIndex, 10, 0)
   105  			if err != nil {
   106  				return nil, fmt.Errorf("error parsing --log-index: %w", err)
   107  			}
   108  			params.LogIndex = logIndexInt
   109  
   110  			resp, err := rekorClient.Entries.GetLogEntryByIndex(params)
   111  			if err != nil {
   112  				return nil, err
   113  			}
   114  			var e models.LogEntryAnon
   115  			for ix, entry := range resp.Payload {
   116  				// verify log entry
   117  				e = entry
   118  				if err := verify.VerifyLogEntry(ctx, &e, verifier); err != nil {
   119  					return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
   120  				}
   121  
   122  				// verify checkpoint
   123  				if entry.Verification.InclusionProof.Checkpoint != nil {
   124  					if err := verify.VerifyCheckpointSignature(&e, verifier); err != nil {
   125  						return nil, err
   126  					}
   127  				}
   128  
   129  				return parseEntry(ix, entry)
   130  			}
   131  		}
   132  
   133  		// Note: this UUID may be an EntryID
   134  		if uuid != "" {
   135  			params := entries.NewGetLogEntryByUUIDParams()
   136  			params.SetTimeout(viper.GetDuration("timeout"))
   137  			params.EntryUUID = uuid
   138  
   139  			resp, err := rekorClient.Entries.GetLogEntryByUUID(params)
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  
   144  			var e models.LogEntryAnon
   145  			for k, entry := range resp.Payload {
   146  				if err := compareEntryUUIDs(params.EntryUUID, k); err != nil {
   147  					return nil, err
   148  				}
   149  
   150  				// verify log entry
   151  				e = entry
   152  				if err := verify.VerifyLogEntry(ctx, &e, verifier); err != nil {
   153  					return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
   154  				}
   155  
   156  				return parseEntry(k, entry)
   157  			}
   158  		}
   159  
   160  		return nil, errors.New("entry not found")
   161  	}),
   162  }
   163  
   164  // TODO: Move this to the verify pkg, but only after sharding cleans
   165  // up it's Trillian client dependency.
   166  func compareEntryUUIDs(requestEntryUUID string, responseEntryUUID string) error {
   167  	requestUUID, err := sharding.GetUUIDFromIDString(requestEntryUUID)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	responseUUID, err := sharding.GetUUIDFromIDString(responseEntryUUID)
   172  	if err != nil {
   173  		return err
   174  	}
   175  	// Compare UUIDs.
   176  	if requestUUID != responseUUID {
   177  		return fmt.Errorf("unexpected entry returned from rekor server: expected %s, got %s", requestEntryUUID, responseEntryUUID)
   178  	}
   179  	// If the request contains a Tree ID, then compare that.
   180  	requestTreeID, err := sharding.GetTreeIDFromIDString(requestEntryUUID)
   181  	if err != nil {
   182  		if errors.Is(err, sharding.ErrPlainUUID) {
   183  			// The request did not contain a Tree ID, we're good.
   184  			return nil
   185  		}
   186  		// The request had a bad Tree ID, error out.
   187  		return err
   188  	}
   189  	// We requested an entry from a given Tree ID.
   190  	responseTreeID, err := sharding.GetTreeIDFromIDString(responseEntryUUID)
   191  	if err != nil {
   192  		if errors.Is(err, sharding.ErrPlainUUID) {
   193  			// The response does not contain a Tree ID, we can only do so much.
   194  			// Old rekor instances may not have returned one.
   195  			return nil
   196  		}
   197  		return err
   198  	}
   199  	// We have Tree IDs. Compare.
   200  	if requestTreeID != responseTreeID {
   201  		return fmt.Errorf("unexpected entry returned from rekor server: expected %s, got %s", requestEntryUUID, responseEntryUUID)
   202  	}
   203  	return nil
   204  }
   205  
   206  func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) {
   207  	if viper.GetString("format") == "tle" {
   208  		return tle.GenerateTransparencyLogEntry(e)
   209  	}
   210  
   211  	b, err := base64.StdEncoding.DecodeString(e.Body.(string))
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	pe, err := models.UnmarshalProposedEntry(bytes.NewReader(b), runtime.JSONConsumer())
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	eimpl, err := types.UnmarshalEntry(pe)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	obj := getCmdOutput{
   226  		Body:           eimpl,
   227  		UUID:           uuid,
   228  		IntegratedTime: *e.IntegratedTime,
   229  		LogIndex:       int(*e.LogIndex),
   230  		LogID:          *e.LogID,
   231  	}
   232  
   233  	if e.Attestation != nil {
   234  		obj.Attestation = string(e.Attestation.Data)
   235  	}
   236  
   237  	return &obj, nil
   238  }
   239  
   240  func init() {
   241  	initializePFlagMap()
   242  	if err := addUUIDPFlags(getCmd, false); err != nil {
   243  		log.CliLogger.Fatal("Error parsing cmd line args: ", err)
   244  	}
   245  	if err := addLogIndexFlag(getCmd, false); err != nil {
   246  		log.CliLogger.Fatal("Error parsing cmd line args: ", err)
   247  	}
   248  
   249  	rootCmd.AddCommand(getCmd)
   250  }
   251  

View as plain text