...

Source file src/github.com/sigstore/rekor/cmd/rekor-cli/app/search.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  	"crypto/sha256"
    20  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"github.com/go-openapi/strfmt"
    30  	"github.com/go-openapi/swag"
    31  	"github.com/spf13/cobra"
    32  	"github.com/spf13/viper"
    33  
    34  	"github.com/sigstore/rekor/cmd/rekor-cli/app/format"
    35  	"github.com/sigstore/rekor/pkg/client"
    36  	"github.com/sigstore/rekor/pkg/generated/client/index"
    37  	"github.com/sigstore/rekor/pkg/generated/models"
    38  	"github.com/sigstore/rekor/pkg/log"
    39  	"github.com/sigstore/rekor/pkg/util"
    40  )
    41  
    42  type searchCmdOutput struct {
    43  	UUIDs []string
    44  }
    45  
    46  func (s *searchCmdOutput) String() string {
    47  	return strings.Join(s.UUIDs, "\n") + "\n" // one extra /n to terminate the list
    48  }
    49  
    50  func addSearchPFlags(cmd *cobra.Command) error {
    51  	cmd.Flags().Var(NewFlagValue(pkiFormatFlag, ""), "pki-format", "format of the signature and/or public key")
    52  
    53  	cmd.Flags().Var(NewFlagValue(fileOrURLFlag, ""), "public-key", "path or URL to public key file")
    54  
    55  	cmd.Flags().Var(NewFlagValue(fileOrURLFlag, ""), "artifact", "path or URL to artifact file")
    56  
    57  	cmd.Flags().Var(NewFlagValue(shaFlag, ""), "sha", "the SHA512, SHA256 or SHA1 sum of the artifact")
    58  
    59  	cmd.Flags().Var(NewFlagValue(emailFlag, ""), "email", "email associated with the public key's subject")
    60  
    61  	cmd.Flags().Var(NewFlagValue(operatorFlag, ""), "operator", "operator to use for the search. supported values are 'and' and 'or'")
    62  	return nil
    63  }
    64  
    65  func validateSearchPFlags() error {
    66  	artifactStr := viper.GetString("artifact")
    67  
    68  	publicKey := viper.GetString("public-key")
    69  	sha := viper.GetString("sha")
    70  	email := viper.GetString("email")
    71  
    72  	if artifactStr == "" && publicKey == "" && sha == "" && email == "" {
    73  		return errors.New("either 'sha' or 'artifact' or 'public-key' or 'email' must be specified")
    74  	}
    75  	if publicKey != "" {
    76  		if viper.GetString("pki-format") == "" {
    77  			return errors.New("pki-format must be specified if searching by public-key")
    78  		}
    79  	}
    80  	return nil
    81  }
    82  
    83  // searchCmd represents the get command
    84  var searchCmd = &cobra.Command{
    85  	Use:   "search",
    86  	Short: "Rekor search command",
    87  	Long:  `Searches the Rekor index to find entries by sha, artifact,  public key, or e-mail`,
    88  	PreRun: func(cmd *cobra.Command, _ []string) {
    89  		// these are bound here so that they are not overwritten by other commands
    90  		if err := viper.BindPFlags(cmd.Flags()); err != nil {
    91  			log.CliLogger.Fatalf("Error initializing cmd line args: %w", err)
    92  		}
    93  		if err := validateSearchPFlags(); err != nil {
    94  			log.CliLogger.Error(err)
    95  			_ = cmd.Help()
    96  			os.Exit(1)
    97  		}
    98  	},
    99  	Run: format.WrapCmd(func(_ []string) (interface{}, error) {
   100  		log := log.CliLogger
   101  		rekorClient, err := client.GetRekorClient(viper.GetString("rekor_server"), client.WithUserAgent(UserAgent()), client.WithRetryCount(viper.GetUint("retry")), client.WithLogger(log))
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  
   106  		params := index.NewSearchIndexParams()
   107  		params.SetTimeout(viper.GetDuration("timeout"))
   108  		params.Query = &models.SearchIndex{}
   109  
   110  		artifactStr := viper.GetString("artifact")
   111  		sha := viper.GetString("sha")
   112  		if sha != "" {
   113  			params.Query.Hash = util.PrefixSHA(sha)
   114  		} else if artifactStr != "" {
   115  			hasher := sha256.New()
   116  			var tee io.Reader
   117  			if isURL(artifactStr) {
   118  				/* #nosec G107 */
   119  				resp, err := http.Get(artifactStr)
   120  				if err != nil {
   121  					return nil, fmt.Errorf("error fetching '%v': %w", artifactStr, err)
   122  				}
   123  				defer resp.Body.Close()
   124  				tee = io.TeeReader(resp.Body, hasher)
   125  			} else {
   126  				file, err := os.Open(filepath.Clean(artifactStr))
   127  				if err != nil {
   128  					return nil, fmt.Errorf("error opening file '%v': %w", artifactStr, err)
   129  				}
   130  				defer func() {
   131  					if err := file.Close(); err != nil {
   132  						log.Error(err)
   133  					}
   134  				}()
   135  
   136  				tee = io.TeeReader(file, hasher)
   137  			}
   138  			if _, err := io.ReadAll(tee); err != nil {
   139  				return nil, fmt.Errorf("error processing '%v': %w", artifactStr, err)
   140  			}
   141  
   142  			hashVal := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
   143  			params.Query.Hash = "sha256:" + hashVal
   144  		}
   145  
   146  		params.Query.Operator = viper.GetString("operator")
   147  
   148  		publicKeyStr := viper.GetString("public-key")
   149  		if publicKeyStr != "" {
   150  			params.Query.PublicKey = &models.SearchIndexPublicKey{}
   151  			pkiFormat := viper.GetString("pki-format")
   152  			switch pkiFormat {
   153  			case "pgp":
   154  				params.Query.PublicKey.Format = swag.String(models.SearchIndexPublicKeyFormatPgp)
   155  			case "minisign":
   156  				params.Query.PublicKey.Format = swag.String(models.SearchIndexPublicKeyFormatMinisign)
   157  			case "x509":
   158  				params.Query.PublicKey.Format = swag.String(models.SearchIndexPublicKeyFormatX509)
   159  			case "ssh":
   160  				params.Query.PublicKey.Format = swag.String(models.SearchIndexPublicKeyFormatSSH)
   161  			case "tuf":
   162  				params.Query.PublicKey.Format = swag.String(models.SearchIndexPublicKeyFormatTUF)
   163  			default:
   164  				return nil, fmt.Errorf("unknown pki-format %v", pkiFormat)
   165  			}
   166  
   167  			splitPubKeyString := strings.Split(publicKeyStr, ",")
   168  			if len(splitPubKeyString) == 1 {
   169  				if isURL(splitPubKeyString[0]) {
   170  					params.Query.PublicKey.URL = strfmt.URI(splitPubKeyString[0])
   171  				} else {
   172  					keyBytes, err := os.ReadFile(filepath.Clean(splitPubKeyString[0]))
   173  					if err != nil {
   174  						return nil, fmt.Errorf("error reading public key file: %w", err)
   175  					}
   176  					params.Query.PublicKey.Content = strfmt.Base64(keyBytes)
   177  				}
   178  			} else {
   179  				return nil, errors.New("only one public key must be provided")
   180  			}
   181  		}
   182  
   183  		emailStr := viper.GetString("email")
   184  		if emailStr != "" {
   185  			params.Query.Email = strfmt.Email(emailStr)
   186  		}
   187  		resp, err := rekorClient.Index.SearchIndex(params)
   188  		if err != nil {
   189  			switch t := err.(type) {
   190  			case *index.SearchIndexDefault:
   191  				if t.Code() == http.StatusNotImplemented {
   192  					return nil, fmt.Errorf("search index not enabled on %v", viper.GetString("rekor_server"))
   193  				}
   194  				return nil, err
   195  			default:
   196  				return nil, err
   197  			}
   198  		}
   199  
   200  		if len(resp.Payload) == 0 {
   201  			return nil, fmt.Errorf("no matching entries found")
   202  		}
   203  
   204  		if viper.GetString("format") != "json" {
   205  			fmt.Fprintln(os.Stderr, "Found matching entries (listed by UUID):")
   206  		}
   207  
   208  		return &searchCmdOutput{
   209  			UUIDs: resp.GetPayload(),
   210  		}, nil
   211  	}),
   212  }
   213  
   214  func init() {
   215  	initializePFlagMap()
   216  	if err := addSearchPFlags(searchCmd); err != nil {
   217  		log.CliLogger.Fatal("Error parsing cmd line args:", err)
   218  	}
   219  
   220  	rootCmd.AddCommand(searchCmd)
   221  }
   222  

View as plain text