diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 62f4a6cdf..abb00744f 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -17,8 +17,11 @@ package app import ( "encoding/base64" + "errors" "fmt" "log" + "os" + "path/filepath" "strconv" "strings" "time" @@ -28,7 +31,7 @@ import ( "github.com/spf13/pflag" - validator "github.com/go-playground/validator/v10" + validator "github.com/asaskevich/govalidator" ) type FlagType string @@ -71,19 +74,38 @@ func initializePFlagMap() { }, operatorFlag: func() pflag.Value { // this validates a valid operator name - return valueFactory(operatorFlag, validateString("oneof=and or"), "") + operatorFlagValidator := func(val string) error { + o := struct { + Value string `valid:"in(and|or)"` + }{val} + _, err := validator.ValidateStruct(o) + return err + } + return valueFactory(operatorFlag, operatorFlagValidator, "") }, emailFlag: func() pflag.Value { // this validates an email address - return valueFactory(emailFlag, validateString("required,email"), "") + emailValidator := func(val string) error { + if !validator.IsEmail(val) { + return fmt.Errorf("'%v' is not a valid email address", val) + } + return nil + } + return valueFactory(emailFlag, emailValidator, "") }, logIndexFlag: func() pflag.Value { // this checks for a valid integer >= 0 - return valueFactory(logIndexFlag, validateLogIndex, "") + return valueFactory(logIndexFlag, validateUint, "") }, pkiFormatFlag: func() pflag.Value { // this ensures a PKI implementation exists for the requested format - return valueFactory(pkiFormatFlag, validateString(fmt.Sprintf("required,oneof=%v", strings.Join(pki.SupportedFormats(), " "))), "pgp") + pkiFormatValidator := func(val string) error { + if !validator.IsIn(val, pki.SupportedFormats()...) { + return fmt.Errorf("'%v' is not a valid pki format", val) + } + return nil + } + return valueFactory(pkiFormatFlag, pkiFormatValidator, "pgp") }, typeFlag: func() pflag.Value { // this ensures the type of the log entry matches a type supported in the CLI @@ -91,11 +113,20 @@ func initializePFlagMap() { }, fileFlag: func() pflag.Value { // this validates that the file exists and can be opened by the current uid - return valueFactory(fileFlag, validateString("required,file"), "") + return valueFactory(fileFlag, validateFile, "") }, urlFlag: func() pflag.Value { // this validates that the string is a valid http/https URL - return valueFactory(urlFlag, validateString("required,url,startswith=http|startswith=https"), "") + httpHTTPSValidator := func(val string) error { + if !validator.IsURL(val) { + return fmt.Errorf("'%v' is not a valid url", val) + } + if !(strings.HasPrefix(val, "http") || strings.HasPrefix(val, "https")) { + return errors.New("URL must be for http or https scheme") + } + return nil + } + return valueFactory(urlFlag, httpHTTPSValidator, "") }, fileOrURLFlag: func() pflag.Value { // applies logic of fileFlag OR urlFlag validators from above @@ -111,7 +142,13 @@ func initializePFlagMap() { }, formatFlag: func() pflag.Value { // this validates the output format requested - return valueFactory(formatFlag, validateString("required,oneof=json default tle"), "") + formatValidator := func(val string) error { + if !validator.IsIn(val, "json", "default", "tle") { + return fmt.Errorf("'%v' is not a valid output format", val) + } + return nil + } + return valueFactory(formatFlag, formatValidator, "") }, timeoutFlag: func() pflag.Value { // this validates the timeout is >= 0 @@ -257,33 +294,23 @@ func validateID(v string) error { return fmt.Errorf("ID len error, expected %v (EntryID) or %v (UUID) but got len %v for ID %v", sharding.EntryIDHexStringLen, sharding.UUIDHexStringLen, len(v), v) } - if err := validateString("required,hexadecimal")(v); err != nil { + if !validator.IsHexadecimal(v) { return fmt.Errorf("invalid uuid: %v", v) } return nil } -// validateLogIndex ensures that the supplied string is a valid log index (integer >= 0) -func validateLogIndex(v string) error { - i, err := strconv.Atoi(v) - if err != nil { - return err - } - l := struct { - Index int `validate:"gte=0"` - }{i} - - return useValidator(logIndexFlag, l) -} - // validateOID ensures that the supplied string is a valid ASN.1 object identifier func validateOID(v string) error { - o := struct { - Oid []string `validate:"dive,numeric"` - }{strings.Split(v, ".")} + values := strings.Split(v, ".") + for _, value := range values { + if !validator.IsNumeric(value) { + return fmt.Errorf("field '%v' is not a valid number", value) + } + } - return useValidator(oidFlag, o) + return nil } // validateTimeout ensures that the supplied string is a valid time.Duration value >= 0 @@ -292,10 +319,10 @@ func validateTimeout(v string) error { if err != nil { return err } - d := struct { - Duration time.Duration `validate:"min=0"` - }{duration} - return useValidator(timeoutFlag, d) + if duration < 0 { + return errors.New("timeout must be a positive value") + } + return nil } // validateBase64 ensures that the supplied string is valid base64 encoded data @@ -312,26 +339,6 @@ func validateTypeFlag(v string) error { return err } -// validateString returns a function that validates an input string against the specified tag, -// as defined in the format supported by go-playground/validator -func validateString(tag string) validationFunc { - return func(v string) error { - validator := validator.New() - return validator.Var(v, tag) - } -} - -// useValidator performs struct level validation on s as defined in the struct's tags using -// the go-playground/validator library -func useValidator(flagType FlagType, s interface{}) error { - validate := validator.New() - if err := validate.Struct(s); err != nil { - return fmt.Errorf("error parsing %v flag: %w", flagType, err) - } - - return nil -} - // validateUint ensures that the supplied string is a valid unsigned integer >= 0 func validateUint(v string) error { i, err := strconv.Atoi(v) @@ -341,9 +348,17 @@ func validateUint(v string) error { if i < 0 { return fmt.Errorf("invalid unsigned int: %v", v) } - u := struct { - Uint uint `validate:"gte=0"` - }{uint(i)} + return nil +} - return useValidator(uintFlag, u) +// validateFile ensures that the supplied string is a valid path to a file that exists +func validateFile(v string) error { + fileInfo, err := os.Stat(filepath.Clean(v)) + if err != nil { + return err + } + if fileInfo.IsDir() { + return errors.New("path to a directory was provided") + } + return nil } diff --git a/cmd/rekor-cli/app/validate.go b/cmd/rekor-cli/app/validate.go index 9ff932585..dafce4af5 100644 --- a/cmd/rekor-cli/app/validate.go +++ b/cmd/rekor-cli/app/validate.go @@ -16,9 +16,11 @@ package app import ( + "errors" + "fmt" "strings" - validator "github.com/go-playground/validator/v10" + validator "github.com/asaskevich/govalidator" ) // validateSHA512Value ensures that the supplied string matches the @@ -36,13 +38,14 @@ func validateSHA512Value(v string) error { hash = split[1] } - s := struct { - Prefix string `validate:"omitempty,oneof=sha512"` - Hash string `validate:"required,len=128,hexadecimal"` - }{prefix, hash} + if strings.TrimSpace(prefix) != "" && prefix != "sha512" { + return fmt.Errorf("invalid prefix '%v'", prefix) + } - validate := validator.New() - return validate.Struct(s) + if !validator.IsSHA512(strings.ToLower(hash)) { + return errors.New("invalid SHA512 value") + } + return nil } // validateSHA256Value ensures that the supplied string matches the following format: @@ -60,13 +63,14 @@ func validateSHA256Value(v string) error { hash = split[1] } - s := struct { - Prefix string `validate:"omitempty,oneof=sha256"` - Hash string `validate:"required,len=64,hexadecimal"` - }{prefix, hash} + if strings.TrimSpace(prefix) != "" && prefix != "sha256" { + return fmt.Errorf("invalid prefix '%v'", prefix) + } - validate := validator.New() - return validate.Struct(s) + if !validator.IsSHA256(strings.ToLower(hash)) { + return errors.New("invalid SHA256 value") + } + return nil } func validateSHA1Value(v string) error { @@ -81,12 +85,12 @@ func validateSHA1Value(v string) error { hash = split[1] } - s := struct { - Prefix string `validate:"omitempty,oneof=sha1"` - Hash string `validate:"required,len=40,hexadecimal"` - }{prefix, hash} - - validate := validator.New() - return validate.Struct(s) + if strings.TrimSpace(prefix) != "" && prefix != "sha1" { + return fmt.Errorf("invalid prefix '%v'", prefix) + } + if !validator.IsSHA1(strings.ToLower(hash)) { + return errors.New("invalid SHA1 value") + } + return nil } diff --git a/go.mod b/go.mod index 057ad038b..e274464ee 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/go-openapi/strfmt v0.21.7 github.com/go-openapi/swag v0.22.4 github.com/go-openapi/validate v0.22.1 - github.com/go-playground/validator/v10 v10.16.0 github.com/google/go-cmp v0.6.0 github.com/google/rpmpack v0.5.0 github.com/google/trillian v1.5.3 @@ -97,7 +96,6 @@ require ( github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -147,8 +145,6 @@ require ( github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -161,7 +157,6 @@ require ( github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect diff --git a/go.sum b/go.sum index 44fd58457..902476c51 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,6 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -241,14 +239,6 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redismock/v9 v9.2.0 h1:ZrMYQeKPECZPjOj5u9eyOjg8Nnb0BS9lkVIZ6IpsKLw= github.com/go-redis/redismock/v9 v9.2.0/go.mod h1:18KHfGDK4Y6c2R0H38EUGWAdc7ZQS9gfYxc94k7rWT0= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= @@ -458,8 +448,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf h1:ndns1qx/5dL43g16EQkPV/i8+b3l5bYQwLeoSBe7tS8= github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf/go.mod h1:aGkAgvWY/IUcVFfuly53REpfv5edu25oij+qHRFaraA= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -596,7 +584,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=