diff --git a/cmd/cicheck.go b/cmd/cicheck.go new file mode 100644 index 0000000..b89a996 --- /dev/null +++ b/cmd/cicheck.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + + "github.com/appknox/appknox-go/helper" + "github.com/spf13/cobra" +) + +// cicheckCmd represents the cicheck command +var cicheckCmd = &cobra.Command{ + Use: "cicheck", + Short: "Check for vulnerabilities based on risk threshold.", + Long: `List all the vulnerabilities with the risk threshold greater or equal than the provided and fail the command.`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("file id is required") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + fileID, err := strconv.Atoi(args[0]) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + riskThreshold, _ := cmd.Flags().GetString("risk_threshold") + riskThresholdLower := strings.ToLower(riskThreshold) + var riskThresholdInt int + switch riskThresholdStr := riskThresholdLower; riskThresholdStr { + case "low": + riskThresholdInt = 1 + case "medium": + riskThresholdInt = 2 + case "high": + riskThresholdInt = 3 + case "critical": + riskThresholdInt = 4 + default: + err := errors.New("valid risk threshold is required") + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + helper.ProcessCiCheck(fileID, riskThresholdInt) + }, +} + +func init() { + RootCmd.AddCommand(cicheckCmd) + cicheckCmd.Flags().StringP( + "risk_threshold", "r", "low", "Risk threshold to fail the command. Available options: low, medium, high") +} diff --git a/helper/analyses.go b/helper/analyses.go index 21706d0..2f89058 100644 --- a/helper/analyses.go +++ b/helper/analyses.go @@ -3,6 +3,7 @@ package helper import ( "context" "fmt" + "os" "github.com/appknox/appknox-go/appknox" "github.com/cheynewallace/tabby" @@ -20,7 +21,8 @@ func ProcessAnalyses(fileID int) { } finalAnalyses, _, err := client.Analyses.ListByFile(ctx, fileID, options) if err != nil { - fmt.Println(err.Error()) + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } t := tabby.New() t.AddHeader( diff --git a/helper/cicheck.go b/helper/cicheck.go new file mode 100644 index 0000000..e2cf503 --- /dev/null +++ b/helper/cicheck.go @@ -0,0 +1,101 @@ +package helper + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "github.com/appknox/appknox-go/appknox" + "github.com/appknox/appknox-go/appknox/enums" + "github.com/cheynewallace/tabby" + "github.com/vbauerster/mpb/v4" + "github.com/vbauerster/mpb/v4/decor" +) + +// ProcessCiCheck takes the list of analyses and print it to CLI. +func ProcessCiCheck(fileID, riskThreshold int) { + ctx := context.Background() + client := getClient() + var staticScanProgess int + start := time.Now() + p := mpb.New( + mpb.WithWidth(60), + mpb.WithRefreshRate(180*time.Millisecond), + mpb.WithOutput(os.Stderr), + ) + name := "Static Scan Progress: " + bar := p.AddBar(100, mpb.BarStyle("[=>-|"), + mpb.PrependDecorators( + decor.Name(name, decor.WC{W: len(name) + 1, C: decor.DidentRight}), + decor.Percentage(), + ), + mpb.AppendDecorators( + decor.Name("] "), + ), + ) + + for staticScanProgess < 100 { + file, _, err := client.Files.GetByID(ctx, fileID) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + staticScanProgess = file.StaticScanProgress + bar.SetCurrent(int64(staticScanProgess), time.Since(start)) + if time.Since(start) > 15*time.Minute { + err := errors.New("Request timed out") + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } + + _, analysisResponse, err := client.Analyses.ListByFile(ctx, fileID, nil) + analysisCount := analysisResponse.GetCount() + options := &appknox.AnalysisListOptions{ + ListOptions: appknox.ListOptions{ + Limit: analysisCount}, + } + finalAnalyses, _, err := client.Analyses.ListByFile(ctx, fileID, options) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + var foundVulnerability bool + t := tabby.New() + t.AddHeader( + "ID", "RISK", "CVSS-VECTOR", + "CVSS-BASE", "VULNERABILITY-ID", + "VULNERABILITY-NAME") + for i := 0; i < len(finalAnalyses); i++ { + if int(finalAnalyses[i].Risk) >= riskThreshold { + foundVulnerability = true + vulnerabilityID := finalAnalyses[i].VulnerabilityID + vulnerability, _, err := client.Vulnerabilities.GetByID(ctx, vulnerabilityID) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + t.AddLine( + finalAnalyses[i].ID, + finalAnalyses[i].Risk, + finalAnalyses[i].CvssVector, + finalAnalyses[i].CvssBase, + vulnerabilityID, + vulnerability.Name, + ) + } + } + if foundVulnerability { + fmt.Println( + "Found vulnerabilities with risk threshold greater or equal than the provided:", enums.RiskType(riskThreshold)) + fmt.Println("") + t.Print() + fmt.Println("") + os.Exit(1) + } else { + fmt.Println( + "No vulnerabilities found with risk threshold greater or equal than the provided:", enums.RiskType(riskThreshold)) + } +} diff --git a/helper/files.go b/helper/files.go index 4056c9f..1819e34 100644 --- a/helper/files.go +++ b/helper/files.go @@ -3,6 +3,7 @@ package helper import ( "context" "fmt" + "os" "github.com/appknox/appknox-go/appknox" "github.com/cheynewallace/tabby" @@ -20,7 +21,8 @@ func ProcessFiles(projectID int, versionCode string, offset, limit int) { } files, _, err := client.Files.ListByProject(ctx, projectID, options) if err != nil { - fmt.Println(err.Error()) + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } t := tabby.New() t.AddHeader( diff --git a/helper/projects.go b/helper/projects.go index aee15c4..2085a61 100644 --- a/helper/projects.go +++ b/helper/projects.go @@ -3,6 +3,7 @@ package helper import ( "context" "fmt" + "os" "time" "github.com/appknox/appknox-go/appknox" @@ -33,7 +34,8 @@ func ProcessProjects(platform, packageName, query string, offset, limit int) { } projects, _, err := client.Projects.List(ctx, options) if err != nil { - fmt.Println(err.Error()) + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } t := tabby.New() t.AddHeader( diff --git a/helper/upload.go b/helper/upload.go index 26262b7..892e61a 100644 --- a/helper/upload.go +++ b/helper/upload.go @@ -20,6 +20,7 @@ func ProcessUpload(file *os.File) { p := mpb.New( mpb.WithWidth(60), mpb.WithRefreshRate(180*time.Millisecond), + mpb.WithOutput(os.Stderr), ) bar := p.AddBar(fileSize, mpb.BarStyle("[=>-|"), mpb.PrependDecorators( @@ -34,15 +35,15 @@ func ProcessUpload(file *os.File) { filewithbar := bar.ProxyReader(file) submissionID, err := client.Upload.UploadFileUsingReader(ctx, filewithbar, fileSize) if err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } akFile, _, err := client.Upload.CheckSubmission(ctx, *submissionID) if err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } t := tabby.New() - t.AddLine("FileID: ", akFile.ID) + t.AddLine(akFile.ID) t.Print() } diff --git a/helper/vulnerability.go b/helper/vulnerability.go index 4abb5b5..46bf8e0 100644 --- a/helper/vulnerability.go +++ b/helper/vulnerability.go @@ -3,6 +3,7 @@ package helper import ( "context" "fmt" + "os" "github.com/cheynewallace/tabby" ) @@ -13,7 +14,8 @@ func ProcessVulnerability(vulnerabilityID int) { client := getClient() vulnerability, _, err := client.Vulnerabilities.GetByID(ctx, vulnerabilityID) if err != nil { - fmt.Println(err.Error()) + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } t := tabby.New() t.AddLine("ID: ", vulnerability.ID)