1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package app
17
18 import (
19 "context"
20 "fmt"
21 "io"
22 "net/http"
23 "net/url"
24 "os"
25 "path/filepath"
26
27 "github.com/go-openapi/runtime"
28 "github.com/go-openapi/swag"
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 gen_client "github.com/sigstore/rekor/pkg/generated/client"
35 "github.com/sigstore/rekor/pkg/generated/client/entries"
36 "github.com/sigstore/rekor/pkg/generated/models"
37 "github.com/sigstore/rekor/pkg/log"
38 "github.com/sigstore/rekor/pkg/types"
39 "github.com/sigstore/rekor/pkg/verify"
40 )
41
42 type uploadCmdOutput struct {
43 AlreadyExists bool
44 Location string
45 Index int64
46 }
47
48 func (u *uploadCmdOutput) String() string {
49 if u.AlreadyExists {
50 return fmt.Sprintf("Entry already exists; available at: %v%v\n", viper.GetString("rekor_server"), u.Location)
51 }
52 return fmt.Sprintf("Created entry at index %d, available at: %v%v\n", u.Index, viper.GetString("rekor_server"), u.Location)
53 }
54
55
56 var uploadCmd = &cobra.Command{
57 Use: "upload",
58 Short: "Upload an artifact to Rekor",
59 PreRunE: func(cmd *cobra.Command, _ []string) error {
60
61 if err := viper.BindPFlags(cmd.Flags()); err != nil {
62 return err
63 }
64 return validateArtifactPFlags(false, false)
65 },
66 Long: `This command takes the public key, signature and URL of the release artifact and uploads it to the rekor server.`,
67 Run: format.WrapCmd(func(_ []string) (interface{}, error) {
68 ctx := context.Background()
69 rekorClient, err := client.GetRekorClient(viper.GetString("rekor_server"), client.WithUserAgent(UserAgent()), client.WithRetryCount(viper.GetUint("retry")), client.WithLogger(log.CliLogger))
70 if err != nil {
71 return nil, err
72 }
73 var entry models.ProposedEntry
74
75 entryStr := viper.GetString("entry")
76 if entryStr != "" {
77 var entryReader io.Reader
78 entryURL, err := url.Parse(entryStr)
79 if err == nil && entryURL.IsAbs() {
80
81 entryResp, err := http.Get(entryStr)
82 if err != nil {
83 return nil, fmt.Errorf("error fetching entry: %w", err)
84 }
85 defer entryResp.Body.Close()
86 entryReader = entryResp.Body
87 } else {
88 entryReader, err = os.Open(filepath.Clean(entryStr))
89 if err != nil {
90 return nil, fmt.Errorf("error processing entry file: %w", err)
91 }
92 }
93 entry, err = models.UnmarshalProposedEntry(entryReader, runtime.JSONConsumer())
94 if err != nil {
95 return nil, fmt.Errorf("error parsing entry file: %w", err)
96 }
97 } else {
98 typeStr, versionStr, err := ParseTypeFlag(viper.GetString("type"))
99 if err != nil {
100 return nil, err
101 }
102
103 props := CreatePropsFromPflags()
104
105 entry, err = types.NewProposedEntry(context.Background(), typeStr, versionStr, *props)
106 if err != nil {
107 return nil, fmt.Errorf("error: %w", err)
108 }
109 }
110 resp, err := tryUpload(rekorClient, entry)
111 if err != nil {
112 switch e := err.(type) {
113 case *entries.CreateLogEntryConflict:
114 return &uploadCmdOutput{
115 Location: e.Location.String(),
116 AlreadyExists: true,
117 }, nil
118 default:
119 return nil, err
120 }
121 }
122
123 var newIndex int64
124 var logEntry models.LogEntryAnon
125 for _, entry := range resp.Payload {
126 newIndex = swag.Int64Value(entry.LogIndex)
127 logEntry = entry
128 }
129
130
131 verifier, err := loadVerifier(rekorClient)
132 if err != nil {
133 return nil, fmt.Errorf("retrieving rekor public key")
134 }
135 if err := verify.VerifySignedEntryTimestamp(ctx, &logEntry, verifier); err != nil {
136 return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
137 }
138
139 if logEntry.Verification.InclusionProof != nil {
140
141 if err := verify.VerifyInclusion(ctx, &logEntry); err != nil {
142 return nil, fmt.Errorf("error verifying inclusion proof: %w", err)
143 }
144
145 if err := verify.VerifyCheckpointSignature(&logEntry, verifier); err != nil {
146 return nil, err
147 }
148 }
149
150 return &uploadCmdOutput{
151 Location: string(resp.Location),
152 Index: newIndex,
153 }, nil
154 }),
155 }
156
157 func tryUpload(rekorClient *gen_client.Rekor, entry models.ProposedEntry) (*entries.CreateLogEntryCreated, error) {
158 params := entries.NewCreateLogEntryParams()
159 params.SetTimeout(viper.GetDuration("timeout"))
160 if pei, ok := entry.(types.ProposedEntryIterator); ok {
161 params.SetProposedEntry(pei.Get())
162 } else {
163 params.SetProposedEntry(entry)
164 }
165 resp, err := rekorClient.Entries.CreateLogEntry(params)
166 if err != nil {
167 if e, ok := err.(*entries.CreateLogEntryBadRequest); ok {
168 if pei, ok := entry.(types.ProposedEntryIterator); ok {
169 if pei.HasNext() {
170 log.CliLogger.Errorf("failed to upload entry: %v", e)
171 return tryUpload(rekorClient, pei.GetNext())
172 }
173 }
174 }
175 return nil, err
176 }
177 return resp, nil
178 }
179
180 func init() {
181 initializePFlagMap()
182 if err := addArtifactPFlags(uploadCmd); err != nil {
183 log.CliLogger.Fatal("Error parsing cmd line args:", err)
184 }
185
186 rootCmd.AddCommand(uploadCmd)
187 }
188
View as plain text