...

Source file src/github.com/sigstore/rekor/cmd/rekor-cli/app/upload.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  	"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  // uploadCmd represents the upload command
    56  var uploadCmd = &cobra.Command{
    57  	Use:   "upload",
    58  	Short: "Upload an artifact to Rekor",
    59  	PreRunE: func(cmd *cobra.Command, _ []string) error {
    60  		// these are bound here so that they are not overwritten by other commands
    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  				/* #nosec G107 */
    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  		// verify log entry
   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  		// TODO: Remove conditional once inclusion proof/checkpoint is always returned by server.
   139  		if logEntry.Verification.InclusionProof != nil {
   140  			// verify inclusion proof
   141  			if err := verify.VerifyInclusion(ctx, &logEntry); err != nil {
   142  				return nil, fmt.Errorf("error verifying inclusion proof: %w", err)
   143  			}
   144  			// verify checkpoint
   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