Skip to content

Commit

Permalink
AST-34271 | improve result summary table in cli (#656)
Browse files Browse the repository at this point in the history
* AST-34271 | improve result summary table in cli

* AST-34271 | fixing pr decoration test

* AST-34271 | fixing triage test, removing uneeded project remove

* AST-34271 | solve getRoot function test bugs
  • Loading branch information
AlvoBen authored Feb 19, 2024
1 parent 3a046db commit aa65ddc
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 71 deletions.
Empty file modified internal/commands/.scripts/integration_down.sh
100644 → 100755
Empty file.
2 changes: 1 addition & 1 deletion internal/commands/.scripts/integration_up.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ rm -rf ScaResolver-linux64.tar.gz
go test \
-tags integration \
-v \
-timeout 60m \
-timeout 90m \
-coverpkg github.com/checkmarx/ast-cli/internal/commands,github.com/checkmarx/ast-cli/internal/wrappers \
-coverprofile cover.out \
github.com/checkmarx/ast-cli/test/integration
Expand Down
181 changes: 122 additions & 59 deletions internal/commands/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,15 @@ const (
highCx = "HIGH"
codeBashingKey = "cb-url"
failedGettingBfl = "Failed getting BFL"
notAvailableString = "N/A"
notAvailableString = "-"
scanFailedString = "Failed"
scanCanceledString = "Canceled"
scanSuccessString = "Completed"
notAvailableNumber = -1
scanFailedNumber = -2
scanCanceledNumber = -3
defaultPaddingSize = -13
defaultResultsPaddingSize = -15
boldFormat = "\033[1m%s\033[0m"
scanPendingMessage = "Scan triggered in asynchronous mode or still running. Click more details to get the full status."
directDependencyType = "Direct Dependency"
indirectDependencyType = "Transitive Dependency"
Expand Down Expand Up @@ -350,6 +355,13 @@ func convertScanToResultsSummary(scanInfo *wrappers.ScanResponseModel, resultsWr
sastIssues := 0
scaIssues := 0
kicsIssues := 0
enginesStatusCode := map[string]int{
commonParams.SastType: 0,
commonParams.ScaType: 0,
commonParams.KicsType: 0,
commonParams.APISecType: 0,
}

if len(scanInfo.StatusDetails) > 0 {
for _, statusDetailItem := range scanInfo.StatusDetails {
if statusDetailItem.Status == wrappers.ScanFailed || statusDetailItem.Status == wrappers.ScanCanceled {
Expand All @@ -361,6 +373,12 @@ func convertScanToResultsSummary(scanInfo *wrappers.ScanResponseModel, resultsWr
kicsIssues = notAvailableNumber
}
}
switch statusDetailItem.Status {
case wrappers.ScanFailed:
handleScanStatus(statusDetailItem, enginesStatusCode, scanFailedNumber)
case wrappers.ScanCanceled:
handleScanStatus(statusDetailItem, enginesStatusCode, scanCanceledNumber)
}
}
}
summary := &wrappers.ResultSummary{
Expand All @@ -381,6 +399,12 @@ func convertScanToResultsSummary(scanInfo *wrappers.ScanResponseModel, resultsWr
ProjectName: scanInfo.ProjectName,
BranchName: scanInfo.Branch,
EnginesEnabled: scanInfo.Engines,
EnginesResult: map[string]*wrappers.EngineResultSummary{
commonParams.SastType: {StatusCode: enginesStatusCode[commonParams.SastType]},
commonParams.ScaType: {StatusCode: enginesStatusCode[commonParams.ScaType]},
commonParams.KicsType: {StatusCode: enginesStatusCode[commonParams.KicsType]},
commonParams.APISecType: {StatusCode: enginesStatusCode[commonParams.APISecType]},
},
}

baseURI, err := resultsWrapper.GetResultsURL(summary.ProjectID)
Expand All @@ -397,6 +421,12 @@ func convertScanToResultsSummary(scanInfo *wrappers.ScanResponseModel, resultsWr
return summary, nil
}

func handleScanStatus(statusDetailItem wrappers.StatusInfo, targetTypes map[string]int, statusCode int) {
if _, ok := targetTypes[statusDetailItem.Name]; ok {
targetTypes[statusDetailItem.Name] = statusCode
}
}

func summaryReport(
summary *wrappers.ResultSummary,
policies *wrappers.PolicyResponseModel,
Expand All @@ -421,13 +451,14 @@ func summaryReport(
setNotAvailableNumberIfZero(summary, &summary.ScaIssues, commonParams.ScaType)
setNotAvailableNumberIfZero(summary, &summary.KicsIssues, commonParams.KicsType)
setRiskMsgAndStyle(summary)
setNotAvailableEnginesStatusCode(summary)

return summary, nil
}

func setNotAvailableNumberIfZero(summary *wrappers.ResultSummary, counter *int, engineType string) {
if *counter == 0 && !contains(summary.EnginesEnabled, engineType) {
*counter = notAvailableNumber
func setNotAvailableEnginesStatusCode(summary *wrappers.ResultSummary) {
for engineName, engineResult := range summary.EnginesResult {
setNotAvailableNumberIfZero(summary, &engineResult.StatusCode, engineName)
}
}

Expand All @@ -446,11 +477,22 @@ func setRiskMsgAndStyle(summary *wrappers.ResultSummary) {
}
}

func setNotAvailableNumberIfZero(summary *wrappers.ResultSummary, counter *int, engineType string) {
if *counter == 0 && !contains(summary.EnginesEnabled, engineType) {
*counter = notAvailableNumber
}
}

func enhanceWithScanSummary(summary *wrappers.ResultSummary, results *wrappers.ScanResultsCollection) {
for _, result := range results.Results {
countResult(summary, result)
}
summary.TotalIssues = summary.SastIssues + summary.ScaIssues + summary.KicsIssues
if summary.HasAPISecurity() {
summary.EnginesResult[commonParams.APISecType].Low = summary.APISecurity.Risks[3]
summary.EnginesResult[commonParams.APISecType].Medium = summary.APISecurity.Risks[2]
summary.EnginesResult[commonParams.APISecType].High = summary.APISecurity.Risks[1]
}
summary.TotalIssues = summary.SastIssues + summary.ScaIssues + summary.KicsIssues + summary.GetAPISecurityDocumentationTotal()
}

func writeHTMLSummary(targetFile string, summary *wrappers.ResultSummary) error {
Expand Down Expand Up @@ -497,63 +539,16 @@ func writeConsoleSummary(summary *wrappers.ResultSummary) error {
" Risk Level: %s \n",
summary.RiskMsg,
)
fmt.Printf(" -------------------------------------- \n")
if summary.HasAPISecurity() {
fmt.Printf(
" API Security - Total Detected APIs: %d \n",
summary.APISecurity.APICount)
}
if summary.Policies != nil && !strings.EqualFold(summary.Policies.Status, policeManagementNoneStatus) {
fmt.Printf(" -------------------------------------- \n\n")
if summary.Policies.BreakBuild {
fmt.Printf(" Policy Management Violation - Break Build Enabled: \n")
} else {
fmt.Printf(" Policy Management Violation: \n")
}
if len(summary.Policies.Polices) > 0 {
for _, police := range summary.Policies.Polices {
if len(police.RulesViolated) > 0 {
fmt.Printf(" Policy: %s | Break Build: %t | Violated Rules: ", police.Name, police.BreakBuild)
for _, violatedRule := range police.RulesViolated {
fmt.Printf("%s;", violatedRule)
}
}
fmt.Printf("\n")
}
}
fmt.Printf("\n")
printPoliciesSummary(summary)
}

fmt.Printf(" Total Results: %d \n", summary.TotalIssues)
fmt.Printf(" -------------------------------------- \n")
fmt.Printf(" | High: %*d| \n", defaultResultsPaddingSize, summary.HighIssues)
fmt.Printf(" | Medium: %*d| \n", defaultResultsPaddingSize, summary.MediumIssues)
fmt.Printf(" | Low: %*d| \n", defaultResultsPaddingSize, summary.LowIssues)
fmt.Printf(" | Info: %*d| \n", defaultResultsPaddingSize, summary.InfoIssues)
fmt.Printf(" -------------------------------------- \n")
printResultsSummaryTable(summary)

if summary.KicsIssues == notAvailableNumber {
fmt.Printf(" | IAC-SECURITY: %*s| \n", defaultPaddingSize, notAvailableString)
} else {
fmt.Printf(" | IAC-SECURITY: %*d| \n", defaultPaddingSize, summary.KicsIssues)
}
if summary.SastIssues == notAvailableNumber {
fmt.Printf(" | SAST: %*s| \n", defaultPaddingSize, notAvailableString)
} else {
fmt.Printf(" | SAST: %*d| \n", defaultPaddingSize, summary.SastIssues)
if summary.HasAPISecurity() {
fmt.Printf(" | APIS WITH RISK: %*d| \n", defaultPaddingSize, summary.APISecurity.TotalRisksCount)
if summary.HasAPISecurityDocumentation() {
fmt.Printf(" | APIS DOCUMENTATION: %*d| \n", defaultPaddingSize, summary.GetAPISecurityDocumentationTotal())
}
}
}
if summary.ScaIssues == notAvailableNumber {
fmt.Printf(" | SCA: %*s| \n", defaultPaddingSize, notAvailableString)
} else {
fmt.Printf(" | SCA: %*d| \n", defaultPaddingSize, summary.ScaIssues)
if summary.HasAPISecurity() {
printAPIsSecuritySummary(summary)
}
fmt.Printf(" -------------------------------------- \n\n")

fmt.Printf(" Checkmarx One - Scan Summary & Details: %s\n", summary.BaseURI)
} else {
fmt.Printf("Scan executed in asynchronous mode or still running. Hence, no results generated.\n")
Expand All @@ -562,6 +557,73 @@ func writeConsoleSummary(summary *wrappers.ResultSummary) error {
return nil
}

func printPoliciesSummary(summary *wrappers.ResultSummary) {
fmt.Printf(" -------------------------------------- \n")
if summary.Policies.BreakBuild {
fmt.Printf(" Policy Management Violation - Break Build Enabled: \n")
} else {
fmt.Printf(" Policy Management Violation: \n")
}
if len(summary.Policies.Polices) > 0 {
for _, police := range summary.Policies.Polices {
if len(police.RulesViolated) > 0 {
fmt.Printf(" Policy: %s | Break Build: %t | Violated Rules: ", police.Name, police.BreakBuild)
for _, violatedRule := range police.RulesViolated {
fmt.Printf("%s;", violatedRule)
}
}
fmt.Printf("\n")
}
}
fmt.Printf("\n")
}

func printAPIsSecuritySummary(summary *wrappers.ResultSummary) {
fmt.Printf(" API Security - Total Detected APIs: %d \n", summary.APISecurity.APICount)
fmt.Printf(" APIS WITH RISK: %*d \n", defaultPaddingSize, summary.APISecurity.TotalRisksCount)
if summary.HasAPISecurityDocumentation() {
fmt.Printf(" APIS DOCUMENTATION: %*d \n", defaultPaddingSize, summary.GetAPISecurityDocumentationTotal())
}
fmt.Printf(" -------------------------------------------------- \n\n")
}

func printTableRow(title string, counts *wrappers.EngineResultSummary, statusNumber int) {
formatString := " | %-4s %4d %6d %4d %4d %-9s |\n"
notAvailableFormatString := " | %-4s %4s %6s %4s %4s %5s |\n"

switch statusNumber {
case notAvailableNumber:
fmt.Printf(notAvailableFormatString, title, notAvailableString, notAvailableString, notAvailableString, notAvailableString, notAvailableString)
case scanFailedNumber:
fmt.Printf(formatString, title, counts.High, counts.Medium, counts.Low, counts.Info, scanFailedString)
case scanCanceledNumber:
fmt.Printf(formatString, title, counts.High, counts.Medium, counts.Low, counts.Info, scanCanceledString)
default:
fmt.Printf(formatString, title, counts.High, counts.Medium, counts.Low, counts.Info, scanSuccessString)
}
}

func printResultsSummaryTable(summary *wrappers.ResultSummary) {
totalHighIssues := summary.EnginesResult.GetHighIssues()
totalMediumIssues := summary.EnginesResult.GetMediumIssues()
totalLowIssues := summary.EnginesResult.GetLowIssues()
totalInfoIssues := summary.EnginesResult.GetInfoIssues()
fmt.Printf(" --------------------------------------------------- \n\n")
fmt.Printf(" Total Results: %d \n", summary.TotalIssues)
fmt.Println(" --------------------------------------------------- ")
fmt.Println(" | High Medium Low Info Status |")

printTableRow("APIs", summary.EnginesResult[commonParams.APISecType], summary.EnginesResult[commonParams.APISecType].StatusCode)
printTableRow("IAC", summary.EnginesResult[commonParams.KicsType], summary.EnginesResult[commonParams.KicsType].StatusCode)
printTableRow("SAST", summary.EnginesResult[commonParams.SastType], summary.EnginesResult[commonParams.SastType].StatusCode)
printTableRow("SCA", summary.EnginesResult[commonParams.ScaType], summary.EnginesResult[commonParams.ScaType].StatusCode)

fmt.Println(" --------------------------------------------------- ")
fmt.Printf(" | %-4s %4d %6d %4d %4d %-9s |\n",
fmt.Sprintf(boldFormat, "TOTAL"), totalHighIssues, totalMediumIssues, totalLowIssues, totalInfoIssues, summary.Status)
fmt.Printf(" --------------------------------------------------- \n\n")
}

func generateScanSummaryURL(summary *wrappers.ResultSummary) string {
summaryURL := fmt.Sprintf(
strings.Replace(summary.BaseURI, "overview", "scans?id=%s&branch=%s", 1),
Expand Down Expand Up @@ -740,6 +802,7 @@ func CreateScanReport(

func countResult(summary *wrappers.ResultSummary, result *wrappers.ScanResult) {
engineType := strings.TrimSpace(result.Type)
severity := strings.ToLower(result.Severity)
if contains(summary.EnginesEnabled, engineType) && isExploitable(result.State) {
if engineType == commonParams.SastType {
summary.SastIssues++
Expand All @@ -751,7 +814,6 @@ func countResult(summary *wrappers.ResultSummary, result *wrappers.ScanResult) {
summary.KicsIssues++
summary.TotalIssues++
}
severity := strings.ToLower(result.Severity)
if severity == highLabel {
summary.HighIssues++
} else if severity == lowLabel {
Expand All @@ -761,6 +823,7 @@ func countResult(summary *wrappers.ResultSummary, result *wrappers.ScanResult) {
} else if severity == infoLabel {
summary.InfoIssues++
}
summary.UpdateEngineResultSummary(engineType, severity)
}
}

Expand Down
62 changes: 61 additions & 1 deletion internal/wrappers/results-summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type ResultSummary struct {
ScanInfoMessage string
EnginesEnabled []string
Policies *PolicyResponseModel
EnginesResult EnginesResultsSummary
}

// nolint: govet
Expand All @@ -40,11 +41,70 @@ type APISecResult struct {
TotalRisksCount int `json:"total_risks_count,omitempty"`
Risks []int `json:"risks,omitempty"`
RiskDistribution []riskDistribution `json:"risk_distribution,omitempty"`
StatusCode int
}
type riskDistribution struct {
Origin string `json:"origin,omitempty"`
Total int `json:"total,omitempty"`
}
type EngineResultSummary struct {
High int
Medium int
Low int
Info int
StatusCode int
}

type EnginesResultsSummary map[string]*EngineResultSummary

func (engineSummary *EnginesResultsSummary) GetHighIssues() int {
highIssues := 0
for _, v := range *engineSummary {
highIssues += v.High
}
return highIssues
}

func (engineSummary *EnginesResultsSummary) GetLowIssues() int {
lowIssues := 0
for _, v := range *engineSummary {
lowIssues += v.Low
}
return lowIssues
}

func (engineSummary *EnginesResultsSummary) GetMediumIssues() int {
mediumIssues := 0
for _, v := range *engineSummary {
mediumIssues += v.Medium
}
return mediumIssues
}

func (engineSummary *EnginesResultsSummary) GetInfoIssues() int {
infoIssues := 0
for _, v := range *engineSummary {
infoIssues += v.Info
}
return infoIssues
}

func (engineSummary *EngineResultSummary) Increment(level string) {
switch level {
case "high":
engineSummary.High++
case "medium":
engineSummary.Medium++
case "low":
engineSummary.Low++
case "info":
engineSummary.Info++
}
}

func (summary *ResultSummary) UpdateEngineResultSummary(engineType, severity string) {
summary.EnginesResult[engineType].Increment(severity)
}

func (r *ResultSummary) HasEngine(engine string) bool {
for _, v := range r.EnginesEnabled {
Expand Down Expand Up @@ -80,7 +140,7 @@ func (r *ResultSummary) GetAPISecurityDocumentationTotal() int {
if riskAPIDocumentation != nil {
return riskAPIDocumentation.Total
}
return -1
return 0
}

func (r *ResultSummary) HasPolicies() bool {
Expand Down
5 changes: 2 additions & 3 deletions test/integration/pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ const (
)

func TestPRGithubDecorationSuccessCase(t *testing.T) {
scanID, _ := getRootScan(t)

scanID, _ := getRootScan(t, params.SastType)
args := []string{
"utils",
"pr",
Expand Down Expand Up @@ -65,7 +64,7 @@ func TestPRGithubDecorationFailure(t *testing.T) {
}

func TestPRGitlabDecorationSuccessCase(t *testing.T) {
scanID, _ := getRootScan(t)
scanID, _ := getRootScan(t, params.SastType)

args := []string{
"utils",
Expand Down
Loading

0 comments on commit aa65ddc

Please sign in to comment.