1
2
3
4
5
6
7
8
9
10
11
12
13
14
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"
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
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
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
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