Skip to content

Commit

Permalink
Scan Results (#262)
Browse files Browse the repository at this point in the history
* - All results processing is handled through "results.go" now.
- The (--report-format) flag determines what kind of report to create:
json, sarif, summaryHTML, summaryConsole.
- The (--output-name) flag determines where to write the report too, if
not provided an appropriate default file name will be used, example:
report.html.
- If (--report-format) is provided to (scan create) them a report file
will be generated.

* - Now creates files based on --output-name, and --output-path arguments.
The name of each report file will be determined by the type of report
and extensions are added automatically.
- The --report-formats options now allow the user to pass multiple
report types as once as a comma-separated list.
- When creating multiple reports the report data will only be downloaded
and prepared once.
  • Loading branch information
tsunez authored Sep 1, 2021
1 parent 5a6f9b0 commit c50a74a
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 327 deletions.
270 changes: 123 additions & 147 deletions internal/commands/result.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package commands

import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"text/template"
Expand All @@ -26,6 +24,9 @@ const (
highLabel = "high"
lowLabel = "low"
sastTypeFlag = "sast"
sastTypeLabel = "sast"
kicsTypeLabel = "infrastructure"
scaTypeLabel = "dependency"
)

var (
Expand Down Expand Up @@ -54,28 +55,13 @@ func NewResultCommand(resultsWrapper wrappers.ResultsWrapper) *cobra.Command {
resultCmd := &cobra.Command{
Use: "result",
Short: "Retrieve results",
RunE: runGetResultCommand(resultsWrapper),
}

listResultsCmd := &cobra.Command{
Use: "list <scan-id>",
Short: "List results for a given scan",
RunE: runGetResultByScanIDCommand(resultsWrapper),
}
listResultsCmd.PersistentFlags().StringSlice(FilterFlag, []string{}, filterResultsListFlagUsage)
addFormatFlag(listResultsCmd, util.FormatList, util.FormatJSON)
addScanIDFlag(listResultsCmd, "ID to report on.")

summaryCmd := &cobra.Command{
Use: "summary",
Short: "Creates summary report for scan",
RunE: runGetSummaryByScanIDCommand(resultsWrapper),
}
summaryCmd.PersistentFlags().StringSlice(FilterFlag, []string{}, filterResultsListFlagUsage)
addFormatFlag(summaryCmd, util.FormatHTML, util.FormatText)
addScanIDFlag(summaryCmd, "ID to report on.")
summaryCmd.PersistentFlags().String(TargetFlag, "console", "Output file")

resultCmd.AddCommand(listResultsCmd, summaryCmd)
addScanIDFlag(resultCmd, "ID to report on.")
addResultFormatFlag(resultCmd, util.FormatJSON, util.FormatSummary, util.FormatSummaryConsole, util.FormatSarif)
resultCmd.PersistentFlags().String(TargetFlag, "", "Output file")
resultCmd.PersistentFlags().String(TargetPathFlag, ".", "Output Path")
resultCmd.PersistentFlags().StringSlice(FilterFlag, []string{}, filterResultsListFlagUsage)
return resultCmd
}

Expand Down Expand Up @@ -107,50 +93,36 @@ func getScanInfo(scanID string) (*ResultSummary, error) {
return nil, err
}

func runGetSummaryByScanIDCommand(resultsWrapper wrappers.ResultsWrapper) func(
cmd *cobra.Command,
args []string,
) error {
return func(cmd *cobra.Command, args []string) error {
targetFile, _ := cmd.Flags().GetString(TargetFlag)
scanID, _ := cmd.Flags().GetString(ScanIDFlag)
format, _ := cmd.Flags().GetString(FormatFlag)
params, err := getFilters(cmd)
if err != nil {
return errors.Wrapf(err, "%s", failedListingResults)
}
results, err := ReadResults(resultsWrapper, scanID, params)
if err == nil {
summary, sumErr := SummaryReport(results, scanID)
if sumErr == nil {
writeSummary(cmd.OutOrStdout(), targetFile, summary, format)
}
return sumErr
}
return err
}
}

func SummaryReport(results *wrappers.ScanResultsCollection, scanID string) (*ResultSummary, error) {
summary, err := getScanInfo(scanID)
if err != nil {
return nil, err
}
summary.TotalIssues = int(results.TotalCount)
for _, result := range results.Results {
if result.Severity == "HIGH" {
if result.Type == sastTypeLabel {
summary.SastIssues++
}
if result.Type == scaTypeLabel {
summary.ScaIssues++
}
if result.Type == kicsTypeLabel {
summary.KicsIssues++
}
severity := strings.ToLower(result.Severity)
if severity == highLabel {
summary.HighIssues++
summary.RiskStyle = highLabel
summary.RiskMsg = "High Risk"
}
if result.Severity == "LOW" {
if severity == lowLabel {
summary.LowIssues++
if summary.RiskStyle != highLabel && summary.RiskStyle != mediumLabel {
summary.RiskStyle = lowLabel
summary.RiskMsg = "Low Risk"
}
}
if result.Severity == mediumLabel {
if severity == mediumLabel {
summary.MediumIssues++
if summary.RiskStyle != highLabel {
summary.RiskStyle = mediumLabel
Expand All @@ -161,82 +133,105 @@ func SummaryReport(results *wrappers.ScanResultsCollection, scanID string) (*Res
return summary, nil
}

func writeSummary(w io.Writer, targetFile string, summary *ResultSummary, format string) {
func writeHTMLSummary(targetFile string, summary *ResultSummary) error {
fmt.Println("Creating Summary Report: ", targetFile)
summaryTemp, err := template.New("summaryTemplate").Parse(summaryTemplate)
if err == nil {
if targetFile == "console" {
if format == util.FormatHTML {
buffer := new(bytes.Buffer)
_ = summaryTemp.ExecuteTemplate(buffer, "SummaryTemplate", summary)
_, _ = fmt.Fprintln(w, buffer)
} else {
writeTextSummary(w, "", summary)
}
} else {
if format == util.FormatHTML {
f, err := os.Create(targetFile)
if err == nil {
_ = summaryTemp.ExecuteTemplate(f, "SummaryTemplate", summary)
_ = f.Close()
}
} else {
writeTextSummary(w, targetFile, summary)
}
}
}
}

func writeTextSummary(w io.Writer, targetFile string, summary *ResultSummary) {
if targetFile != "" {
f, err := os.Create(targetFile)
if err == nil {
sumMsg := ""
sumMsg += fmt.Sprintf(" Created At: %s\n", summary.CreatedAt)

sumMsg += fmt.Sprintf(" Risk: %s\n", summary.RiskMsg)
sumMsg += fmt.Sprintf(" Project ID: %s\n", summary.ProjectID)
sumMsg += fmt.Sprintf(" Scan ID: %s\n", summary.ScanID)
sumMsg += fmt.Sprintf(" Total Issues: %d\n", summary.TotalIssues)
sumMsg += fmt.Sprintf(" High Issues: %d\n", summary.HighIssues)
sumMsg += fmt.Sprintf(" Medium Issues: %d\n", summary.MediumIssues)
sumMsg += fmt.Sprintf(" Low Issues: %d\n", summary.LowIssues)
sumMsg += fmt.Sprintf(" SAST Issues: %d\n", summary.SastIssues)
sumMsg += fmt.Sprintf(" KICS Issues: %d\n", summary.KicsIssues)
sumMsg += fmt.Sprintf(" SCA Issues: %d\n", summary.ScaIssues)
_, _ = f.WriteString(sumMsg)
_ = summaryTemp.ExecuteTemplate(f, "SummaryTemplate", summary)
_ = f.Close()
}
} else {
_, _ = fmt.Fprintf(w, " Created At: %s\n", summary.CreatedAt)
_, _ = fmt.Fprintf(w, " Risk: %s\n", summary.RiskMsg)
_, _ = fmt.Fprintf(w, " Project ID: %s\n", summary.ProjectID)
_, _ = fmt.Fprintf(w, " Scan ID: %s\n", summary.ScanID)
_, _ = fmt.Fprintf(w, " Total Issues: %d\n", summary.TotalIssues)
_, _ = fmt.Fprintf(w, " High Issues: %d\n", summary.HighIssues)
_, _ = fmt.Fprintf(w, " Medium Issues: %d\n", summary.MediumIssues)
_, _ = fmt.Fprintf(w, " Low Issues: %d\n", summary.LowIssues)
_, _ = fmt.Fprintf(w, " SAST Issues: %d\n", summary.SastIssues)
_, _ = fmt.Fprintf(w, " KICS Issues: %d\n", summary.KicsIssues)
_, _ = fmt.Fprintf(w, " SCA Issues: %d\n", summary.ScaIssues)
return err
}
return nil
}

func runGetResultByScanIDCommand(resultsWrapper wrappers.ResultsWrapper) func(cmd *cobra.Command, args []string) error {
func writeConsoleSummary(summary *ResultSummary) error {
fmt.Println("")
fmt.Printf(" Created At: %s\n", summary.CreatedAt)
fmt.Printf(" Risk: %s\n", summary.RiskMsg)
fmt.Printf(" Project ID: %s\n", summary.ProjectID)
fmt.Printf(" Scan ID: %s\n", summary.ScanID)
fmt.Printf(" Total Issues: %d\n", summary.TotalIssues)
fmt.Printf(" High Issues: %d\n", summary.HighIssues)
fmt.Printf(" Medium Issues: %d\n", summary.MediumIssues)
fmt.Printf(" Low Issues: %d\n", summary.LowIssues)

fmt.Printf(" Kics Issues: %d\n", summary.KicsIssues)
fmt.Printf(" Sast Issues: %d\n", summary.SastIssues)
fmt.Printf(" SCA Issues: %d\n", summary.ScaIssues)

return nil
}

func runGetResultCommand(resultsWrapper wrappers.ResultsWrapper) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
targetFile, _ := cmd.Flags().GetString(TargetFlag)
targetPath, _ := cmd.Flags().GetString(TargetPathFlag)
format, _ := cmd.Flags().GetString(TargetFormatFlag)
scanID, _ := cmd.Flags().GetString(ScanIDFlag)
if scanID == "" {
return errors.Errorf("%s: Please provide a scan ID", failedListingResults)
}
params, err := getFilters(cmd)
if err != nil {
return errors.Wrapf(err, "%s", failedListingResults)
}
results, err := ReadResults(resultsWrapper, scanID, params)
if err == nil {
return exportResults(cmd, results)
}
return CreateScanReport(resultsWrapper, scanID, format, targetFile, targetPath, params)
}
}

func CreateScanReport(resultsWrapper wrappers.ResultsWrapper,
scanID string,
reportTypes string,
targetFile string,
targetPath string,
params map[string]string) error {
if scanID == "" {
return errors.Errorf("%s: Please provide a scan ID", failedListingResults)
}
results, err := ReadResults(resultsWrapper, scanID, params)
if err != nil {
return err
}
summary, err := SummaryReport(results, scanID)
if err != nil {
return err
}
reportList := strings.Split(reportTypes, ",")
for _, reportType := range reportList {
err = createReport(reportType, targetFile, targetPath, results, summary)
if err != nil {
return err
}
}
return nil
}

func createReport(format string,
targetFile string,
targetPath string,
results *wrappers.ScanResultsCollection,
summary *ResultSummary) error {
if util.IsFormat(format, util.FormatSarif) {
sarifRpt := createTargetName(targetFile, targetPath, "sarif")
return exportSarifResults(sarifRpt, results)
}
if util.IsFormat(format, util.FormatJSON) {
jsonRpt := createTargetName(targetFile, targetPath, "json")
return exportJSONResults(jsonRpt, results)
}
if util.IsFormat(format, util.FormatSummaryConsole) {
return writeConsoleSummary(summary)
}
if util.IsFormat(format, util.FormatSummary) {
summaryRpt := createTargetName(targetFile, targetPath, "html")
return writeHTMLSummary(summaryRpt, summary)
}
err := fmt.Errorf("bad report format %s", format)
return err
}

func createTargetName(targetFile, targetPath, targetType string) string {
return targetPath + "/" + targetFile + "." + targetType
}

func ReadResults(
Expand All @@ -259,46 +254,38 @@ func ReadResults(
return nil, nil
}

func exportResults(cmd *cobra.Command, results *wrappers.ScanResultsCollection) error {
formatFlag, _ := cmd.Flags().GetString(FormatFlag)
if util.IsFormat(formatFlag, util.FormatJSON) {
return exportJSONResults(cmd, results)
}
if util.IsFormat(formatFlag, util.FormatSarif) {
return exportSarifResults(cmd, results)
}
return outputResultsPretty(cmd.OutOrStdout(), results.Results)
}

func exportSarifResults(cmd *cobra.Command, results *wrappers.ScanResultsCollection) error {
func exportSarifResults(targetFile string, results *wrappers.ScanResultsCollection) error {
var err error
var resultsJSON []byte
fmt.Println("Creating SARIF Report: ", targetFile)
var sarifResults *wrappers.SarifResultsCollection = convertCxResultsToSarif(results)
resultsJSON, err = json.Marshal(sarifResults)
if err != nil {
return errors.Wrapf(err, "%s: failed to serialize results response ", failedGettingAll)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), string(resultsJSON))
f, err := os.Create(targetFile)
if err != nil {
return errors.Wrapf(err, "%s: failed to create target file ", failedGettingAll)
}
_, _ = fmt.Fprintln(f, string(resultsJSON))
f.Close()
return nil
}

func exportJSONResults(cmd *cobra.Command, results *wrappers.ScanResultsCollection) error {
func exportJSONResults(targetFile string, results *wrappers.ScanResultsCollection) error {
var err error
var resultsJSON []byte
fmt.Println("Creating JSON Report: ", targetFile)
resultsJSON, err = json.Marshal(results)
if err != nil {
return errors.Wrapf(err, "%s: failed to serialize results response ", failedGettingAll)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), string(resultsJSON))
return nil
}

func outputResultsPretty(w io.Writer, results []*wrappers.ScanResult) error {
_, _ = fmt.Fprintln(w, "************ Results ************")
for i := 0; i < len(results); i++ {
outputSingleResult(w, results[i])
_, _ = fmt.Fprintln(w)
f, err := os.Create(targetFile)
if err != nil {
return errors.Wrapf(err, "%s: failed to create target file ", failedGettingAll)
}
_, _ = fmt.Fprintln(f, string(resultsJSON))
f.Close()
return nil
}

Expand Down Expand Up @@ -358,7 +345,10 @@ func findSarifResults(results *wrappers.ScanResultsCollection) []wrappers.SarifS
// this is placeholder code
scanLocation.PhysicalLocation.ArtifactLocation.URI = ""
scanLocation.PhysicalLocation.Region.StartLine = result.ScanResultData.Nodes[0].Line
column := result.ScanResultData.Nodes[0].Column
// TODO: fix this column issue and places that reference it when
// the data structures are fixed.
// column := result.ScanResultData.Nodes[0].Column
var column uint = 0
length := result.ScanResultData.Nodes[0].Length
scanLocation.PhysicalLocation.Region.StartColumn = column
scanLocation.PhysicalLocation.Region.EndColumn = column + length
Expand All @@ -368,20 +358,6 @@ func findSarifResults(results *wrappers.ScanResultsCollection) []wrappers.SarifS
return sarifResults
}

func outputSingleResult(w io.Writer, model *wrappers.ScanResult) {
_, _ = fmt.Fprintln(w, "Result Unique ID:", model.ScanResultData.PackageID)
_, _ = fmt.Fprintln(w, "Query ID:", model.ScanResultData.QueryID)
_, _ = fmt.Fprintln(w, "Query Name:", model.ScanResultData.QueryName)
_, _ = fmt.Fprintln(w, "Severity:", model.Severity)
_, _ = fmt.Fprintln(w, "Similarity ID:", model.SimilarityID)
_, _ = fmt.Fprintln(w, "First Scan ID:", model.FirstScanID)
_, _ = fmt.Fprintln(w, "Found At:", model.FoundAt)
_, _ = fmt.Fprintln(w, "First Found At:", model.FirstFoundAt)
_, _ = fmt.Fprintln(w, "Status:", model.Status)
_, _ = fmt.Fprintln(w)
_, _ = fmt.Fprintln(w, "************ Nodes ************")
}

type ResultSummary struct {
TotalIssues int
HighIssues int
Expand Down
Loading

0 comments on commit c50a74a

Please sign in to comment.