Skip to content
Snippets Groups Projects
Unverified Commit 53d71cd8 authored by Bob Callaway's avatar Bob Callaway Committed by GitHub
Browse files

Improve separation between type implementations and CLI code (#339)


* Refactor PKI factory and add type checking

This allows for more DRY addition of new PKI types, and stricter
type checking. This also allows for simpler enumeration of
supported PKI formats which will be used in further updates to
simplify the CLI codebase.

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>

* revamp CLI flags; support different versions for upload

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>

* Add Alpine Package type

This adds support for the alpine package format used by Alpine Linux,
which is the concatenation of three tgz files (signature, control data,
and then the actual package files).

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>

* use shaFlag for --artifact-hash

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>

* change arg type to PKIFormat

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>

* defer type-specific validation logic to type code (instead of in CLI); also use CliLogger throughout CLI

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>

* refactor factory code

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>

* review comments

Signed-off-by: default avatarBob Callaway <bob.callaway@gmail.com>
parent e63fe717
No related branches found
No related tags found
No related merge requests found
Showing
with 729 additions and 992 deletions
......@@ -18,8 +18,8 @@ package format
import (
"encoding/json"
"fmt"
"log"
"github.com/sigstore/rekor/pkg/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
......@@ -32,7 +32,7 @@ func WrapCmd(f formatCmd) CobraCmd {
return func(cmd *cobra.Command, args []string) {
obj, err := f(args)
if err != nil {
log.Fatal(err)
log.CliLogger.Fatal(err)
}
// TODO: add flags to control output formatting (JSON, plaintext, etc.)
......@@ -53,7 +53,7 @@ func WrapCmd(f formatCmd) CobraCmd {
func toJSON(i interface{}) string {
b, err := json.Marshal(i)
if err != nil {
log.Fatal(err)
log.CliLogger.Fatal(err)
}
return string(b)
}
......@@ -74,7 +74,7 @@ var getCmd = &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
// these are bound here so that they are not overwritten by other commands
if err := viper.BindPFlags(cmd.Flags()); err != nil {
log.Logger.Fatal("Error initializing cmd line args: ", err)
log.CliLogger.Fatal("Error initializing cmd line args: ", err)
}
},
Run: format.WrapCmd(func(args []string) (interface{}, error) {
......@@ -161,11 +161,12 @@ func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) {
}
func init() {
initializePFlagMap()
if err := addUUIDPFlags(getCmd, false); err != nil {
log.Logger.Fatal("Error parsing cmd line args:", err)
log.CliLogger.Fatal("Error parsing cmd line args: ", err)
}
if err := addLogIndexFlag(getCmd, false); err != nil {
log.Logger.Fatal("Error parsing cmd line args:", err)
log.CliLogger.Fatal("Error parsing cmd line args: ", err)
}
rootCmd.AddCommand(getCmd)
......
......@@ -153,5 +153,6 @@ var logInfoCmd = &cobra.Command{
}
func init() {
initializePFlagMap()
rootCmd.AddCommand(logInfoCmd)
}
......@@ -98,6 +98,7 @@ var logProofCmd = &cobra.Command{
}
func init() {
initializePFlagMap()
logProofCmd.Flags().Uint64("first-size", 1, "the size of the log where the proof should begin")
logProofCmd.Flags().Uint64("last-size", 0, "the size of the log where the proof should end")
if err := logProofCmd.MarkFlagRequired("last-size"); err != nil {
......
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package app
import (
"errors"
"fmt"
"net/url"
"strings"
"github.com/sigstore/rekor/pkg/pki"
"github.com/sigstore/rekor/pkg/types"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// addFlagToCmd adds the specified command of a specified type to the command's flag set
func addFlagToCmd(cmd *cobra.Command, required bool, flagType FlagType, flag, desc string) error {
cmd.Flags().Var(NewFlagValue(flagType, ""), flag, desc)
if required {
return cmd.MarkFlagRequired(flag)
}
return nil
}
// addLogIndexFlag adds the "log-index" command to the command's flag set
func addLogIndexFlag(cmd *cobra.Command, required bool) error {
return addFlagToCmd(cmd, required, logIndexFlag, "log-index", "the index of the entry in the transparency log")
}
// addUUIDPFlags adds the "uuid" command to the command's flag set
func addUUIDPFlags(cmd *cobra.Command, required bool) error {
return addFlagToCmd(cmd, required, uuidFlag, "uuid", "UUID of entry in transparency log (if known)")
}
func addArtifactPFlags(cmd *cobra.Command) error {
flags := map[string]struct {
flagType FlagType
desc string
required bool
}{
"signature": {
fileOrURLFlag,
"path or URL to detached signature file",
false,
},
"type": {
typeFlag,
fmt.Sprintf("type of entry expressed as type(:version)?; supported types = %v", types.ListImplementedTypes()),
false,
},
"pki-format": {
pkiFormatFlag,
fmt.Sprintf("format of the signature and/or public key; options = %v", pki.SupportedFormats()),
false,
},
"public-key": {
fileOrURLFlag,
"path or URL to public key file",
false,
},
"artifact": {
fileOrURLFlag,
"path or URL to artifact file",
false,
},
"artifact-hash": {
shaFlag,
"hex encoded SHA256 hash of artifact (when using URL)",
false,
},
"entry": {
fileOrURLFlag,
"path or URL to pre-formatted entry file",
false,
},
}
for flag, flagVal := range flags {
if err := addFlagToCmd(cmd, flagVal.required, flagVal.flagType, flag, flagVal.desc); err != nil {
return err
}
}
return nil
}
func validateArtifactPFlags(uuidValid, indexValid bool) error {
uuidGiven := false
if uuidValid && viper.GetString("uuid") != "" {
uuidGiven = true
}
indexGiven := false
if indexValid && viper.GetString("log-index") != "" {
indexGiven = true
}
// if neither --entry or --artifact were given, then a reference to a uuid or index is needed
if viper.GetString("entry") == "" && viper.GetString("artifact") == "" {
if (uuidGiven && uuidValid) || (indexGiven && indexValid) {
return nil
}
return errors.New("either 'entry' or 'artifact' must be specified")
}
return nil
}
func CreatePropsFromPflags() *types.ArtifactProperties {
props := &types.ArtifactProperties{}
artifactString := viper.GetString("artifact")
if artifactString != "" {
if isURL(artifactString) {
props.ArtifactPath, _ = url.Parse(artifactString)
} else {
props.ArtifactPath = &url.URL{Path: artifactString}
}
}
props.ArtifactHash = viper.GetString("artifact-hash")
signatureString := viper.GetString("signature")
if signatureString != "" {
if isURL(signatureString) {
props.SignaturePath, _ = url.Parse(signatureString)
} else {
props.SignaturePath = &url.URL{Path: signatureString}
}
}
publicKeyString := viper.GetString("public-key")
if publicKeyString != "" {
if isURL(publicKeyString) {
props.PublicKeyPath, _ = url.Parse(publicKeyString)
} else {
props.PublicKeyPath = &url.URL{Path: publicKeyString}
}
}
props.PKIFormat = viper.GetString("pki-format")
return props
}
//TODO: add tests for this
func ParseTypeFlag(typeStr string) (string, string, error) {
// typeStr can come in as:
// type -> use default version for this kind
// type:version_string -> attempt to use specified version string
typeStrings := strings.SplitN(typeStr, ":", 2)
if _, ok := types.TypeMap.Load(typeStrings[0]); !ok {
return "", "", fmt.Errorf("unknown type %v", typeStrings[0])
}
switch len(typeStrings) {
case 1:
return typeStrings[0], "", nil
case 2:
return typeStrings[0], typeStrings[1], nil
}
return "", "", errors.New("malformed type string")
}
This diff is collapsed.
......@@ -16,6 +16,7 @@
package app
import (
"context"
"errors"
"io/ioutil"
"net/http"
......@@ -25,7 +26,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/types"
)
func TestArtifactPFlags(t *testing.T) {
......@@ -348,6 +349,7 @@ func TestArtifactPFlags(t *testing.T) {
}
for _, tc := range tests {
initializePFlagMap()
var blankCmd = &cobra.Command{}
if err := addArtifactPFlags(blankCmd); err != nil {
t.Fatalf("unexpected error adding flags in '%v': %v", tc.caseDesc, err)
......@@ -396,27 +398,13 @@ func TestArtifactPFlags(t *testing.T) {
t.Errorf("unexpected result validating '%v': %v", tc.caseDesc, err)
continue
}
if !tc.uuidRequired && !tc.logIndexRequired {
var createFn func() (models.ProposedEntry, error)
switch tc.typeStr {
case "", "rekord":
createFn = CreateRekordFromPFlags
case "rpm":
createFn = CreateRpmFromPFlags
case "jar":
createFn = CreateJarFromPFlags
case "intoto":
createFn = CreateIntotoFromPFlags
case "rfc3161":
createFn = CreateRFC3161FromPFlags
case "alpine":
createFn = CreateAlpineFromPFlags
case "helm":
createFn = CreateHelmFromPFlags
default:
t.Fatalf("type %v not implemented", tc.typeStr)
if !tc.uuidRequired && !tc.logIndexRequired && tc.entry == "" {
typeStr, versionStr, err := ParseTypeFlag(viper.GetString("type"))
if err != nil {
t.Errorf("error parsing typeStr: %v", err)
}
if _, err := createFn(); err != nil {
props := CreatePropsFromPflags()
if _, err := types.NewProposedEntry(context.Background(), typeStr, versionStr, *props); err != nil {
t.Errorf("unexpected result in '%v' building entry: %v", tc.caseDesc, err)
}
}
......@@ -577,7 +565,7 @@ func TestSearchPFlags(t *testing.T) {
caseDesc: "invalid email",
email: "SignaMeseCat",
expectParseSuccess: false,
expectValidateSuccess: true,
expectValidateSuccess: false,
},
{
caseDesc: "no flags when either artifact, sha, public key, or email are needed",
......@@ -587,6 +575,7 @@ func TestSearchPFlags(t *testing.T) {
}
for _, tc := range tests {
initializePFlagMap()
var blankCmd = &cobra.Command{}
if err := addSearchPFlags(blankCmd); err != nil {
t.Fatalf("unexpected error adding flags in '%v': %v", tc.caseDesc, err)
......
......@@ -16,16 +16,24 @@
package app
import (
"errors"
"fmt"
"net/url"
"os"
"strings"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/sigstore/rekor/pkg/log"
// these imports are to call the packages' init methods
_ "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1"
_ "github.com/sigstore/rekor/pkg/types/helm/v0.0.1"
_ "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1"
_ "github.com/sigstore/rekor/pkg/types/jar/v0.0.1"
_ "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
_ "github.com/sigstore/rekor/pkg/types/rfc3161/v0.0.1"
_ "github.com/sigstore/rekor/pkg/types/rpm/v0.0.1"
)
var rootCmd = &cobra.Command{
......@@ -40,24 +48,23 @@ var rootCmd = &cobra.Command{
// Execute runs the base CLI
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
log.CliLogger.Fatal(err)
}
}
func init() {
initializePFlagMap()
rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.rekor.yaml)")
rootCmd.PersistentFlags().Bool("store_tree_state", true, "whether to store tree state in between invocations for additional verification")
rootCmd.PersistentFlags().Var(&urlFlag{url: "https://rekor.sigstore.dev"}, "rekor_server", "Server address:port")
rootCmd.PersistentFlags().Var(&formatFlag{format: "default"}, "format", "Command output format")
rootCmd.PersistentFlags().Var(NewFlagValue(urlFlag, "https://rekor.sigstore.dev"), "rekor_server", "Server address:port")
rootCmd.PersistentFlags().Var(NewFlagValue(formatFlag, "default"), "format", "Command output format")
rootCmd.PersistentFlags().String("api-key", "", "API key for rekor.sigstore.dev")
// these are bound here and not in PreRun so that all child commands can use them
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
fmt.Println(err)
os.Exit(1)
log.CliLogger.Fatal(err)
}
}
......@@ -102,64 +109,8 @@ func initConfig(cmd *cobra.Command) error {
return err
}
} else if viper.GetString("format") == "default" {
fmt.Println("Using config file:", viper.ConfigFileUsed())
log.CliLogger.Infof("Using config file:", viper.ConfigFileUsed())
}
return nil
}
type urlFlag struct {
url string
}
func (u *urlFlag) String() string {
return u.url
}
func (u *urlFlag) Set(s string) error {
if s == "" {
return errors.New("flag must be specified")
}
url, err := url.Parse(s)
if err != nil {
return fmt.Errorf("malformed URL: %w", err)
}
if !url.IsAbs() {
return fmt.Errorf("URL must be absolute, got %s", s)
}
lowercaseScheme := strings.ToLower(url.Scheme)
if lowercaseScheme != "http" && lowercaseScheme != "https" {
return fmt.Errorf("URL must be a valid HTTP or HTTPS URL, got %s", s)
}
u.url = s
return nil
}
func (u *urlFlag) Type() string {
return "url"
}
type formatFlag struct {
format string
}
func (f *formatFlag) String() string {
return f.format
}
func (f *formatFlag) Set(s string) error {
choices := map[string]struct{}{"default": {}, "json": {}}
if s == "" {
f.format = "default"
return nil
}
if _, ok := choices[s]; ok {
f.format = s
return nil
}
return fmt.Errorf("invalid flag value: %s, valid values are [default, json]", s)
}
func (f *formatFlag) Type() string {
return "format"
}
......@@ -18,6 +18,7 @@ package app
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
......@@ -53,6 +54,37 @@ func (s *searchCmdOutput) String() string {
return str
}
func addSearchPFlags(cmd *cobra.Command) error {
cmd.Flags().Var(NewFlagValue(pkiFormatFlag, ""), "pki-format", "format of the signature and/or public key")
cmd.Flags().Var(NewFlagValue(fileOrURLFlag, ""), "public-key", "path or URL to public key file")
cmd.Flags().Var(NewFlagValue(fileOrURLFlag, ""), "artifact", "path or URL to artifact file")
cmd.Flags().Var(NewFlagValue(shaFlag, ""), "sha", "the SHA256 sum of the artifact")
cmd.Flags().Var(NewFlagValue(emailFlag, ""), "email", "email associated with the public key's subject")
return nil
}
func validateSearchPFlags() error {
artifactStr := viper.GetString("artifact")
publicKey := viper.GetString("public-key")
sha := viper.GetString("sha")
email := viper.GetString("email")
if artifactStr == "" && publicKey == "" && sha == "" && email == "" {
return errors.New("either 'sha' or 'artifact' or 'public-key' or 'email' must be specified")
}
if publicKey != "" {
if viper.GetString("pki-format") == "" {
return errors.New("pki-format must be specified if searching by public-key")
}
}
return nil
}
// searchCmd represents the get command
var searchCmd = &cobra.Command{
Use: "search",
......@@ -61,16 +93,16 @@ var searchCmd = &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
// these are bound here so that they are not overwritten by other commands
if err := viper.BindPFlags(cmd.Flags()); err != nil {
log.Logger.Fatal("Error initializing cmd line args: ", err)
log.CliLogger.Fatal("Error initializing cmd line args: ", err)
}
if err := validateSearchPFlags(); err != nil {
log.Logger.Error(err)
log.CliLogger.Error(err)
_ = cmd.Help()
os.Exit(1)
}
},
Run: format.WrapCmd(func(args []string) (interface{}, error) {
log := log.Logger
log := log.CliLogger
rekorClient, err := client.GetRekorClient(viper.GetString("rekor_server"))
if err != nil {
return nil, err
......@@ -84,25 +116,21 @@ var searchCmd = &cobra.Command{
if sha != "" {
params.Query.Hash = sha
} else if artifactStr != "" {
artifact := fileOrURLFlag{}
if err := artifact.Set(artifactStr); err != nil {
return nil, err
}
hasher := sha256.New()
var tee io.Reader
if artifact.IsURL {
if isURL(artifactStr) {
/* #nosec G107 */
resp, err := http.Get(artifact.String())
resp, err := http.Get(artifactStr)
if err != nil {
return nil, fmt.Errorf("error fetching '%v': %w", artifact.String(), err)
return nil, fmt.Errorf("error fetching '%v': %w", artifactStr, err)
}
defer resp.Body.Close()
tee = io.TeeReader(resp.Body, hasher)
} else {
file, err := os.Open(filepath.Clean(artifact.String()))
file, err := os.Open(filepath.Clean(artifactStr))
if err != nil {
return nil, fmt.Errorf("error opening file '%v': %w", artifact.String(), err)
return nil, fmt.Errorf("error opening file '%v': %w", artifactStr, err)
}
defer func() {
if err := file.Close(); err != nil {
......@@ -113,7 +141,7 @@ var searchCmd = &cobra.Command{
tee = io.TeeReader(file, hasher)
}
if _, err := ioutil.ReadAll(tee); err != nil {
return nil, fmt.Errorf("error processing '%v': %w", artifact.String(), err)
return nil, fmt.Errorf("error processing '%v': %w", artifactStr, err)
}
hashVal := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
......@@ -136,14 +164,11 @@ var searchCmd = &cobra.Command{
default:
return nil, fmt.Errorf("unknown pki-format %v", pkiFormat)
}
publicKey := fileOrURLFlag{}
if err := publicKey.Set(publicKeyStr); err != nil {
return nil, err
}
if publicKey.IsURL {
params.Query.PublicKey.URL = strfmt.URI(publicKey.String())
publicKeyStr := viper.GetString("public-key")
if isURL(publicKeyStr) {
params.Query.PublicKey.URL = strfmt.URI(publicKeyStr)
} else {
keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKey.String()))
keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKeyStr))
if err != nil {
return nil, fmt.Errorf("error reading public key file: %w", err)
}
......@@ -175,8 +200,9 @@ var searchCmd = &cobra.Command{
}
func init() {
initializePFlagMap()
if err := addSearchPFlags(searchCmd); err != nil {
log.Logger.Fatal("Error parsing cmd line args:", err)
log.CliLogger.Fatal("Error parsing cmd line args:", err)
}
rootCmd.AddCommand(searchCmd)
......
......@@ -23,7 +23,6 @@ import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
......@@ -40,59 +39,11 @@ import (
"github.com/spf13/viper"
)
type fileFlag struct {
value string
}
func (f *fileFlag) String() string {
return f.value
}
func (f *fileFlag) Set(s string) error {
if s == "" {
return errors.New("flag must be specified")
}
if _, err := os.Stat(filepath.Clean(s)); os.IsNotExist(err) {
return err
}
f.value = s
return nil
}
func (f *fileFlag) Type() string {
return "fileFlag"
}
type oidFlag struct {
value asn1.ObjectIdentifier
}
func (f *oidFlag) String() string {
return f.value.String()
}
func (f *oidFlag) Set(s string) error {
parts := strings.Split(s, ".")
f.value = make(asn1.ObjectIdentifier, len(parts))
for i, part := range parts {
num, err := strconv.Atoi(part)
if err != nil {
return errors.New("error parsing OID")
}
f.value[i] = num
}
return nil
}
func (f *oidFlag) Type() string {
return "oidFlag"
}
func addTimestampFlags(cmd *cobra.Command) error {
cmd.Flags().Var(&fileFlag{}, "artifact", "path to an artifact to timestamp")
cmd.Flags().Var(&uuidFlag{}, "artifact-hash", "hex encoded SHA256 hash of the the artifact to timestamp")
cmd.Flags().Var(NewFlagValue(fileFlag, ""), "artifact", "path to an artifact to timestamp")
cmd.Flags().Var(NewFlagValue(shaFlag, ""), "artifact-hash", "hex encoded SHA256 hash of the the artifact to timestamp")
cmd.Flags().Bool("nonce", true, "specify a pseudo-random nonce in the request")
cmd.Flags().Var(&oidFlag{}, "tsa-policy", "optional dotted OID notation for the policy that the TSA should use to create the response")
cmd.Flags().Var(NewFlagValue(oidFlag, ""), "tsa-policy", "optional dotted OID notation for the policy that the TSA should use to create the response")
cmd.Flags().String("out", "response.tsr", "path to a file to write response.")
......@@ -108,21 +59,6 @@ func validateTimestampFlags() error {
return errors.New("artifact or hash to timestamp must be specified")
}
if digestStr != "" {
digest := uuidFlag{}
if err := digest.Set(digestStr); err != nil {
return err
}
}
policyStr := viper.GetString("tsa-policy")
if policyStr != "" {
oid := oidFlag{}
if err := oid.Set(policyStr); err != nil {
return err
}
}
return nil
}
......@@ -136,11 +72,12 @@ func createRequestFromFlags() (*pkcs9.TimeStampReq, error) {
Hash: crypto.SHA256,
}
if policyStr != "" {
oid := oidFlag{}
if err := oid.Set(policyStr); err != nil {
return nil, err
var oidInts []int
for _, v := range strings.Split(policyStr, ".") {
i, _ := strconv.Atoi(v)
oidInts = append(oidInts, i)
}
opts.TSAPolicyOid = oid.value
opts.TSAPolicyOid = oidInts
}
if viper.GetBool("nonce") {
opts.Nonce = x509tools.MakeSerial()
......@@ -193,7 +130,7 @@ var timestampCmd = &cobra.Command{
Long: "Generates and uploads (WIP) an RFC 3161 timestamp response to the log. The timestamp response can be verified locally using Rekor's timestamping cert chain.",
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := viper.BindPFlags(cmd.Flags()); err != nil {
log.Logger.Fatal("Error initializing cmd line args: ", err)
log.CliLogger.Fatal("Error initializing cmd line args: ", err)
}
if err := validateTimestampFlags(); err != nil {
log.Logger.Error(err)
......@@ -253,8 +190,9 @@ var timestampCmd = &cobra.Command{
}
func init() {
initializePFlagMap()
if err := addTimestampFlags(timestampCmd); err != nil {
log.Logger.Fatal("Error parsing cmd line args: ", err)
log.CliLogger.Fatal("Error parsing cmd line args: ", err)
}
rootCmd.AddCommand(timestampCmd)
......
......@@ -19,7 +19,12 @@ import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path/filepath"
"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/go-openapi/swag"
......@@ -33,6 +38,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/types"
"github.com/sigstore/rekor/pkg/util"
)
......@@ -70,49 +76,46 @@ var uploadCmd = &cobra.Command{
if err != nil {
return nil, err
}
var entry models.ProposedEntry
params := entries.NewCreateLogEntryParams()
var entry models.ProposedEntry
switch viper.GetString("type") {
case "rekord":
entry, err = CreateRekordFromPFlags()
if err != nil {
return nil, err
}
case "rpm":
entry, err = CreateRpmFromPFlags()
if err != nil {
return nil, err
}
case "jar":
entry, err = CreateJarFromPFlags()
if err != nil {
return nil, err
entryStr := viper.GetString("entry")
if entryStr != "" {
var entryBytes []byte
entryURL, err := url.Parse(entryStr)
if err == nil && entryURL.IsAbs() {
/* #nosec G107 */
entryResp, err := http.Get(entryStr)
if err != nil {
return nil, fmt.Errorf("error fetching entry: %w", err)
}
defer entryResp.Body.Close()
entryBytes, err = ioutil.ReadAll(entryResp.Body)
if err != nil {
return nil, fmt.Errorf("error fetching entry: %w", err)
}
} else {
entryBytes, err = ioutil.ReadFile(filepath.Clean(entryStr))
if err != nil {
return nil, fmt.Errorf("error processing entry file: %w", err)
}
}
case "intoto":
entry, err = CreateIntotoFromPFlags()
if err != nil {
return nil, err
if err := json.Unmarshal(entryBytes, &entry); err != nil {
return nil, fmt.Errorf("error parsing entry file: %w", err)
}
case "rfc3161":
entry, err = CreateRFC3161FromPFlags()
} else {
typeStr, versionStr, err := ParseTypeFlag(viper.GetString("type"))
if err != nil {
return nil, err
}
case "alpine":
entry, err = CreateAlpineFromPFlags()
if err != nil {
return nil, err
}
case "helm":
entry, err = CreateHelmFromPFlags()
props := CreatePropsFromPflags()
entry, err = types.NewProposedEntry(context.Background(), typeStr, versionStr, *props)
if err != nil {
return nil, err
}
default:
return nil, errors.New("unknown type specified")
}
params.SetProposedEntry(entry)
resp, err := rekorClient.Entries.CreateLogEntry(params)
......@@ -187,8 +190,9 @@ func verifyLogEntry(ctx context.Context, rekorClient *genclient.Rekor, logEntry
}
func init() {
initializePFlagMap()
if err := addArtifactPFlags(uploadCmd); err != nil {
log.Logger.Fatal("Error parsing cmd line args:", err)
log.CliLogger.Fatal("Error parsing cmd line args:", err)
}
rootCmd.AddCommand(uploadCmd)
......
......@@ -16,8 +16,8 @@
package app
import (
"context"
"encoding/hex"
"errors"
"fmt"
"math/bits"
"strconv"
......@@ -32,6 +32,7 @@ import (
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/types"
)
type verifyCmdOutput struct {
......@@ -104,40 +105,16 @@ var verifyCmd = &cobra.Command{
}
searchLogQuery.LogIndexes = []*int64{&logIndexInt}
} else {
var entry models.ProposedEntry
switch viper.GetString("type") {
case "rekord":
entry, err = CreateRekordFromPFlags()
if err != nil {
return nil, err
}
case "rpm":
entry, err = CreateRpmFromPFlags()
if err != nil {
return nil, err
}
case "jar":
entry, err = CreateJarFromPFlags()
if err != nil {
return nil, err
}
case "intoto":
entry, err = CreateIntotoFromPFlags()
if err != nil {
return nil, err
}
case "rfc3161":
entry, err = CreateRFC3161FromPFlags()
if err != nil {
return nil, err
}
case "alpine":
entry, err = CreateAlpineFromPFlags()
if err != nil {
return nil, err
}
default:
return nil, errors.New("invalid type specified")
typeStr, versionStr, err := ParseTypeFlag(viper.GetString("type"))
if err != nil {
return nil, err
}
props := CreatePropsFromPflags()
entry, err := types.NewProposedEntry(context.Background(), typeStr, versionStr, *props)
if err != nil {
return nil, err
}
entries := []models.ProposedEntry{entry}
......@@ -186,14 +163,15 @@ var verifyCmd = &cobra.Command{
}
func init() {
initializePFlagMap()
if err := addArtifactPFlags(verifyCmd); err != nil {
log.Logger.Fatal("Error parsing cmd line args:", err)
log.CliLogger.Fatal("Error parsing cmd line args:", err)
}
if err := addUUIDPFlags(verifyCmd, false); err != nil {
log.Logger.Fatal("Error parsing cmd line args:", err)
log.CliLogger.Fatal("Error parsing cmd line args:", err)
}
if err := addLogIndexFlag(verifyCmd, false); err != nil {
log.Logger.Fatal("Error parsing cmd line args:", err)
log.CliLogger.Fatal("Error parsing cmd line args:", err)
}
rootCmd.AddCommand(verifyCmd)
......
......@@ -93,7 +93,7 @@ services:
# "--log_type=prod",
]
volumes:
- "/var/run/attestations:/var/run/attestations"
- "/var/run/attestations:/var/run/attestations:z"
restart: always # keep the server running
ports:
- "3000:3000"
......
......@@ -46,6 +46,7 @@ const (
signingError = "Error signing"
failedToGenerateTimestampResponse = "Error generating timestamp response"
sthGenerateError = "Error generating signed tree head"
unsupportedPKIFormat = "The PKI format requested is not supported by this server"
)
func errorMsg(message string, code int) *models.Error {
......
......@@ -45,7 +45,10 @@ func SearchIndexHandler(params index.SearchIndexParams) middleware.Responder {
result = append(result, resultUUIDs...)
}
if params.Query.PublicKey != nil {
af := pki.NewArtifactFactory(swag.StringValue(params.Query.PublicKey.Format))
af, err := pki.NewArtifactFactory(pki.Format(swag.StringValue(params.Query.PublicKey.Format)))
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, unsupportedPKIFormat)
}
keyReader, err := util.FileOrURLReadCloser(httpReqCtx, params.Query.PublicKey.URL.String(), params.Query.PublicKey.Content)
if err != nil {
return handleRekorAPIError(params, http.StatusBadRequest, err, malformedPublicKey)
......
......@@ -56,6 +56,7 @@ func createCliLogger() *zap.SugaredLogger {
cfg.EncoderConfig.TimeKey = ""
cfg.EncoderConfig.LevelKey = ""
cfg.DisableCaller = true
cfg.DisableStacktrace = true
logger, err := cfg.Build()
if err != nil {
log.Fatalln("createLogger", err)
......
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pki
import (
"fmt"
"io"
"github.com/sigstore/rekor/pkg/pki/minisign"
"github.com/sigstore/rekor/pkg/pki/pgp"
"github.com/sigstore/rekor/pkg/pki/pkcs7"
"github.com/sigstore/rekor/pkg/pki/ssh"
"github.com/sigstore/rekor/pkg/pki/x509"
)
type Format string
const (
PGP Format = "pgp"
Minisign Format = "minisign"
SSH Format = "ssh"
X509 Format = "x509"
PKCS7 Format = "pkcs7"
)
type ArtifactFactory struct {
impl pkiImpl
}
func NewArtifactFactory(format Format) (*ArtifactFactory, error) {
if impl, ok := artifactFactoryMap[format]; ok {
return &ArtifactFactory{impl: impl}, nil
}
return nil, fmt.Errorf("%v is not a supported PKI format", format)
}
type pkiImpl struct {
newPubKey func(io.Reader) (PublicKey, error)
newSignature func(io.Reader) (Signature, error)
}
var artifactFactoryMap map[Format]pkiImpl
func init() {
artifactFactoryMap = map[Format]pkiImpl{
PGP: {
newPubKey: func(r io.Reader) (PublicKey, error) {
return pgp.NewPublicKey(r)
},
newSignature: func(r io.Reader) (Signature, error) {
return pgp.NewSignature(r)
},
},
Minisign: {
newPubKey: func(r io.Reader) (PublicKey, error) {
return minisign.NewPublicKey(r)
},
newSignature: func(r io.Reader) (Signature, error) {
return minisign.NewSignature(r)
},
},
SSH: {
newPubKey: func(r io.Reader) (PublicKey, error) {
return ssh.NewPublicKey(r)
},
newSignature: func(r io.Reader) (Signature, error) {
return ssh.NewSignature(r)
},
},
X509: {
newPubKey: func(r io.Reader) (PublicKey, error) {
return x509.NewPublicKey(r)
},
newSignature: func(r io.Reader) (Signature, error) {
return x509.NewSignature(r)
},
},
PKCS7: {
newPubKey: func(r io.Reader) (PublicKey, error) {
return pkcs7.NewPublicKey(r)
},
newSignature: func(r io.Reader) (Signature, error) {
return pkcs7.NewSignature(r)
},
},
}
}
func SupportedFormats() []string {
var formats []string
for f := range artifactFactoryMap {
formats = append(formats, string(f))
}
return formats
}
func (a ArtifactFactory) NewPublicKey(r io.Reader) (PublicKey, error) {
return a.impl.newPubKey(r)
}
func (a ArtifactFactory) NewSignature(r io.Reader) (Signature, error) {
return a.impl.newSignature(r)
}
//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pki
import (
"os"
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestFactoryNewKey(t *testing.T) {
type TestCase struct {
name string
format string
keyFile string
sigFile string
expectSuccess bool
expectValidFormat bool
}
testCases := []TestCase{
{
name: "valid pgp",
format: "pgp",
keyFile: "pgp/testdata/valid_armored_public.pgp",
sigFile: "pgp/testdata/hello_world.txt.asc.sig",
expectSuccess: true,
expectValidFormat: true,
},
{
name: "valid minisign",
format: "minisign",
keyFile: "minisign/testdata/minisign.pub",
sigFile: "minisign/testdata/hello_world.txt.minisig",
expectSuccess: true,
expectValidFormat: true,
},
{
name: "valid x509",
format: "x509",
keyFile: "x509/testdata/ec.pub",
sigFile: "x509/testdata/hello_world.txt.sig",
expectSuccess: true,
expectValidFormat: true,
},
{
name: "valid ssh",
format: "ssh",
keyFile: "ssh/testdata/id_rsa.pub",
sigFile: "ssh/testdata/hello_world.txt.sig",
expectSuccess: true,
expectValidFormat: true,
},
{
name: "invalid ssh signature",
format: "ssh",
keyFile: "ssh/testdata/id_rsa.pub",
sigFile: "ssh/testdata/hello_world.txt",
expectSuccess: false,
expectValidFormat: true,
},
{
name: "invalid ssh key",
format: "ssh",
keyFile: "ssh/testdata/hello_world.txt",
sigFile: "ssh/testdata/hello_world.txt.sig",
expectSuccess: false,
expectValidFormat: true,
},
{
format: "bogus",
expectSuccess: false,
expectValidFormat: false,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
factory, err := NewArtifactFactory(Format(tc.format))
if tc.expectValidFormat != (err == nil) {
t.Fatalf("unexpected error initializing factory for %v", tc.format)
}
if factory != nil {
keyFile, _ := os.Open(tc.keyFile)
_, newKeyErr := factory.NewPublicKey(keyFile)
sigFile, _ := os.Open(tc.sigFile)
_, newSigErr := factory.NewSignature(sigFile)
if tc.expectSuccess {
if newKeyErr != nil || newSigErr != nil {
t.Errorf("unexpected error generating public key %v or signature %v", newKeyErr, newSigErr)
}
} else { // expect a failure{
if newKeyErr == nil && newSigErr == nil {
t.Error("expected error generating public key and signature. got none")
}
}
}
})
}
}
......@@ -23,7 +23,7 @@ import (
"strings"
minisign "github.com/jedisct1/go-minisign"
"github.com/sigstore/sigstore/pkg/signature"
sigsig "github.com/sigstore/sigstore/pkg/signature"
)
// Signature Signature that follows the minisign standard; supports both minisign and signify generated signatures
......@@ -98,7 +98,7 @@ func (s Signature) Verify(r io.Reader, k interface{}) error {
return fmt.Errorf("minisign public key has not been initialized")
}
verifier, err := signature.LoadED25519Verifier(key.key.PublicKey[:])
verifier, err := sigsig.LoadED25519Verifier(key.key.PublicKey[:])
if err != nil {
return err
}
......
......@@ -301,8 +301,4 @@ func TestVerifySignature(t *testing.T) {
if err := validSig.Verify(bytes.NewReader([]byte("irrelevant")), &emptyKey); err == nil {
t.Errorf("expected error when using empty key to verify")
}
if err := validSig.Verify(bytes.NewReader([]byte("irrelevant")), sigFile); err == nil {
t.Errorf("expected error when using non key to verify")
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment