From 208bb251185198dba4dee2c0d1f6bea08423161c Mon Sep 17 00:00:00 2001 From: dwertent Date: Sun, 27 Feb 2022 16:33:21 +0200 Subject: [PATCH 1/2] fixed junit support --- resultshandling/printer/v1/junit.go | 125 -------- resultshandling/printer/v1/prettyprinter.go | 271 ------------------ resultshandling/printer/v1/printresults.go | 3 - resultshandling/printer/v1/silentprinter.go | 11 - resultshandling/printer/v1/summary.go | 54 ---- resultshandling/printer/v1/summeryhelpers.go | 85 ------ resultshandling/printer/v2/junit.go | 154 +++++----- .../printer/v2/resourcemapping/jsonprinter.go | 41 --- resultshandling/results.go | 2 +- 9 files changed, 76 insertions(+), 670 deletions(-) delete mode 100644 resultshandling/printer/v1/junit.go delete mode 100644 resultshandling/printer/v1/prettyprinter.go delete mode 100644 resultshandling/printer/v1/printresults.go delete mode 100644 resultshandling/printer/v1/silentprinter.go delete mode 100644 resultshandling/printer/v1/summary.go delete mode 100644 resultshandling/printer/v1/summeryhelpers.go delete mode 100644 resultshandling/printer/v2/resourcemapping/jsonprinter.go diff --git a/resultshandling/printer/v1/junit.go b/resultshandling/printer/v1/junit.go deleted file mode 100644 index baee79fd..00000000 --- a/resultshandling/printer/v1/junit.go +++ /dev/null @@ -1,125 +0,0 @@ -package v1 - -import ( - "encoding/xml" - "fmt" - "os" - - "github.com/armosec/kubescape/cautils" - "github.com/armosec/kubescape/cautils/logger" - "github.com/armosec/kubescape/resultshandling/printer" - "github.com/armosec/opa-utils/reporthandling" -) - -type JunitPrinter struct { - writer *os.File -} - -func NewJunitPrinter() *JunitPrinter { - return &JunitPrinter{} -} - -func (junitPrinter *JunitPrinter) SetWriter(outputFile string) { - junitPrinter.writer = printer.GetWriter(outputFile) -} - -func (junitPrinter *JunitPrinter) Score(score float32) { - fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score)) -} - -func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { - cautils.ReportV2ToV1(opaSessionObj) - - junitResult, err := convertPostureReportToJunitResult(opaSessionObj.PostureReport) - if err != nil { - logger.L().Fatal("failed to convert posture report object") - } - postureReportStr, err := xml.Marshal(junitResult) - if err != nil { - logger.L().Fatal("failed to convert posture report object") - } - junitPrinter.writer.Write(postureReportStr) -} - -type JUnitTestSuites struct { - XMLName xml.Name `xml:"testsuites"` - Suites []JUnitTestSuite `xml:"testsuite"` -} - -// JUnitTestSuite is a single JUnit test suite which may contain many -// testcases. -type JUnitTestSuite struct { - XMLName xml.Name `xml:"testsuite"` - Tests int `xml:"tests,attr"` - Time string `xml:"time,attr"` - Name string `xml:"name,attr"` - Resources int `xml:"resources,attr"` - Excluded int `xml:"excluded,attr"` - Failed int `xml:"filed,attr"` - Properties []JUnitProperty `xml:"properties>property,omitempty"` - TestCases []JUnitTestCase `xml:"testcase"` -} - -// JUnitTestCase is a single test case with its result. -type JUnitTestCase struct { - XMLName xml.Name `xml:"testcase"` - Classname string `xml:"classname,attr"` - Name string `xml:"name,attr"` - Time string `xml:"time,attr"` - Resources int `xml:"resources,attr"` - Excluded int `xml:"excluded,attr"` - Failed int `xml:"filed,attr"` - SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` - Failure *JUnitFailure `xml:"failure,omitempty"` -} - -// JUnitSkipMessage contains the reason why a testcase was skipped. -type JUnitSkipMessage struct { - Message string `xml:"message,attr"` -} - -// JUnitProperty represents a key/value pair used to define properties. -type JUnitProperty struct { - Name string `xml:"name,attr"` - Value string `xml:"value,attr"` -} - -// JUnitFailure contains data related to a failed test. -type JUnitFailure struct { - Message string `xml:"message,attr"` - Type string `xml:"type,attr"` - Contents string `xml:",chardata"` -} - -func convertPostureReportToJunitResult(postureResult *reporthandling.PostureReport) (*JUnitTestSuites, error) { - juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}} - for _, framework := range postureResult.FrameworkReports { - suite := JUnitTestSuite{ - Name: framework.Name, - Resources: framework.GetNumberOfResources(), - Excluded: framework.GetNumberOfWarningResources(), - Failed: framework.GetNumberOfFailedResources(), - } - for _, controlReports := range framework.ControlReports { - suite.Tests = suite.Tests + 1 - testCase := JUnitTestCase{} - testCase.Name = controlReports.Name - testCase.Classname = "Kubescape" - testCase.Time = postureResult.ReportGenerationTime.String() - if 0 < len(controlReports.RuleReports) && 0 < len(controlReports.RuleReports[0].RuleResponses) { - testCase.Resources = controlReports.GetNumberOfResources() - testCase.Excluded = controlReports.GetNumberOfWarningResources() - testCase.Failed = controlReports.GetNumberOfFailedResources() - failure := JUnitFailure{} - failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed) - for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses { - failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage) - } - testCase.Failure = &failure - } - suite.TestCases = append(suite.TestCases, testCase) - } - juResult.Suites = append(juResult.Suites, suite) - } - return &juResult, nil -} diff --git a/resultshandling/printer/v1/prettyprinter.go b/resultshandling/printer/v1/prettyprinter.go deleted file mode 100644 index d6cb8580..00000000 --- a/resultshandling/printer/v1/prettyprinter.go +++ /dev/null @@ -1,271 +0,0 @@ -package v1 - -import ( - "fmt" - "os" - "sort" - "strings" - - "github.com/armosec/k8s-interface/workloadinterface" - "github.com/armosec/kubescape/cautils" - "github.com/armosec/kubescape/resultshandling/printer" - "github.com/armosec/opa-utils/objectsenvelopes" - "github.com/armosec/opa-utils/reporthandling" - "github.com/enescakir/emoji" - "github.com/olekukonko/tablewriter" -) - -type PrettyPrinter struct { - writer *os.File - summary Summary - verboseMode bool - sortedControlNames []string - frameworkSummary ResultSummary -} - -func NewPrettyPrinter(verboseMode bool) *PrettyPrinter { - return &PrettyPrinter{ - verboseMode: verboseMode, - summary: NewSummary(), - } -} - -// !!!! DEPRECATED !!!! use v2 package -func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { - overallRiskScore := opaSessionObj.Report.SummaryDetails.Score - cautils.ReportV2ToV1(opaSessionObj) - - // score := calculatePostureScore(opaSessionObj.PostureReport) - failedResources := []string{} - warningResources := []string{} - allResources := []string{} - frameworkNames := []string{} - frameworkScores := []float32{} - - for _, frameworkReport := range opaSessionObj.PostureReport.FrameworkReports { - frameworkNames = append(frameworkNames, frameworkReport.Name) - frameworkScores = append(frameworkScores, frameworkReport.Score) - failedResources = reporthandling.GetUniqueResourcesIDs(append(failedResources, frameworkReport.ListResourcesIDs().GetFailedResources()...)) - warningResources = reporthandling.GetUniqueResourcesIDs(append(warningResources, frameworkReport.ListResourcesIDs().GetWarningResources()...)) - allResources = reporthandling.GetUniqueResourcesIDs(append(allResources, frameworkReport.ListResourcesIDs().GetAllResources()...)) - prettyPrinter.summarySetup(frameworkReport, opaSessionObj.AllResources) - } - - prettyPrinter.frameworkSummary = ResultSummary{ - RiskScore: overallRiskScore, - TotalResources: len(allResources), - TotalFailed: len(failedResources), - TotalWarning: len(warningResources), - } - - prettyPrinter.printResults() - prettyPrinter.printSummaryTable(frameworkNames, frameworkScores) - -} - -func (prettyPrinter *PrettyPrinter) SetWriter(outputFile string) { - prettyPrinter.writer = printer.GetWriter(outputFile) -} - -func (prettyPrinter *PrettyPrinter) Score(score float32) { -} - -func (prettyPrinter *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport, allResources map[string]workloadinterface.IMetadata) { - - for _, cr := range fr.ControlReports { - // if len(cr.RuleReports) == 0 { - // continue - // } - workloadsSummary := listResultSummary(cr.RuleReports, allResources) - - var passedWorkloads map[string][]WorkloadSummary - if prettyPrinter.verboseMode { - passedWorkloads = groupByNamespaceOrKind(workloadsSummary, workloadSummaryPassed) - } - - //controlSummary - prettyPrinter.summary[cr.Name] = ResultSummary{ - ID: cr.ControlID, - RiskScore: cr.Score, - TotalResources: cr.GetNumberOfResources(), - TotalFailed: cr.GetNumberOfFailedResources(), - TotalWarning: cr.GetNumberOfWarningResources(), - FailedWorkloads: groupByNamespaceOrKind(workloadsSummary, workloadSummaryFailed), - ExcludedWorkloads: groupByNamespaceOrKind(workloadsSummary, workloadSummaryExclude), - PassedWorkloads: passedWorkloads, - Description: cr.Description, - Remediation: cr.Remediation, - ListInputKinds: cr.ListControlsInputKinds(), - } - - } - prettyPrinter.sortedControlNames = prettyPrinter.getSortedControlsNames() -} -func (prettyPrinter *PrettyPrinter) printResults() { - for i := 0; i < len(prettyPrinter.sortedControlNames); i++ { - controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]] - prettyPrinter.printTitle(prettyPrinter.sortedControlNames[i], &controlSummary) - prettyPrinter.printResources(&controlSummary) - if prettyPrinter.summary[prettyPrinter.sortedControlNames[i]].TotalResources > 0 { - prettyPrinter.printSummary(prettyPrinter.sortedControlNames[i], &controlSummary) - } - - } -} - -func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary *ResultSummary) { - cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ") - cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.TotalResources-controlSummary.TotalFailed-controlSummary.TotalWarning) - cautils.WarningDisplay(prettyPrinter.writer, "Excluded:%v ", controlSummary.TotalWarning) - cautils.FailureDisplay(prettyPrinter.writer, "Failed:%v ", controlSummary.TotalFailed) - cautils.InfoDisplay(prettyPrinter.writer, "Total:%v\n", controlSummary.TotalResources) - if controlSummary.TotalFailed > 0 { - cautils.DescriptionDisplay(prettyPrinter.writer, "Remediation: %v\n", controlSummary.Remediation) - } - cautils.DescriptionDisplay(prettyPrinter.writer, "\n") - -} -func (prettyPrinter *PrettyPrinter) printTitle(controlName string, controlSummary *ResultSummary) { - cautils.InfoDisplay(prettyPrinter.writer, "[control: %s - %s] ", controlName, getControlURL(controlSummary.ID)) - if controlSummary.TotalResources == 0 { - cautils.InfoDisplay(prettyPrinter.writer, "skipped %v\n", emoji.ConfusedFace) - } else if controlSummary.TotalFailed != 0 { - cautils.FailureDisplay(prettyPrinter.writer, "failed %v\n", emoji.SadButRelievedFace) - } else if controlSummary.TotalWarning != 0 { - cautils.WarningDisplay(prettyPrinter.writer, "excluded %v\n", emoji.NeutralFace) - } else { - cautils.SuccessDisplay(prettyPrinter.writer, "passed %v\n", emoji.ThumbsUp) - } - - cautils.DescriptionDisplay(prettyPrinter.writer, "Description: %s\n", controlSummary.Description) - -} -func (prettyPrinter *PrettyPrinter) printResources(controlSummary *ResultSummary) { - - if len(controlSummary.FailedWorkloads) > 0 { - cautils.FailureDisplay(prettyPrinter.writer, "Failed:\n") - prettyPrinter.printGroupedResources(controlSummary.FailedWorkloads) - } - if len(controlSummary.ExcludedWorkloads) > 0 { - cautils.WarningDisplay(prettyPrinter.writer, "Excluded:\n") - prettyPrinter.printGroupedResources(controlSummary.ExcludedWorkloads) - } - if len(controlSummary.PassedWorkloads) > 0 { - cautils.SuccessDisplay(prettyPrinter.writer, "Passed:\n") - prettyPrinter.printGroupedResources(controlSummary.PassedWorkloads) - } - -} - -func (prettyPrinter *PrettyPrinter) printGroupedResources(workloads map[string][]WorkloadSummary) { - indent := INDENT - for title, rsc := range workloads { - prettyPrinter.printGroupedResource(indent, title, rsc) - } -} - -func (prettyPrinter *PrettyPrinter) printGroupedResource(indent string, title string, rsc []WorkloadSummary) { - preIndent := indent - if title != "" { - cautils.SimpleDisplay(prettyPrinter.writer, "%s%s\n", indent, title) - indent += indent - } - - for r := range rsc { - relatedObjectsStr := generateRelatedObjectsStr(rsc[r]) - cautils.SimpleDisplay(prettyPrinter.writer, fmt.Sprintf("%s%s - %s %s\n", indent, rsc[r].resource.GetKind(), rsc[r].resource.GetName(), relatedObjectsStr)) - } - indent = preIndent -} - -func generateRelatedObjectsStr(workload WorkloadSummary) string { - relatedStr := "" - if workload.resource.GetObjectType() == workloadinterface.TypeWorkloadObject { - relatedObjects := objectsenvelopes.NewRegoResponseVectorObject(workload.resource.GetObject()).GetRelatedObjects() - for i, related := range relatedObjects { - if ns := related.GetNamespace(); i == 0 && ns != "" { - relatedStr += fmt.Sprintf("Namespace - %s, ", ns) - } - relatedStr += fmt.Sprintf("%s - %s, ", related.GetKind(), related.GetName()) - } - } - if relatedStr != "" { - relatedStr = fmt.Sprintf(" [%s]", relatedStr[:len(relatedStr)-2]) - } - return relatedStr -} - -func generateRow(control string, cs ResultSummary) []string { - row := []string{control} - row = append(row, cs.ToSlice()...) - if cs.TotalResources != 0 { - row = append(row, fmt.Sprintf("%d", int(cs.RiskScore))+"%") - } else { - row = append(row, "skipped") - } - return row -} - -func generateHeader() []string { - return []string{"Control Name", "Failed Resources", "Excluded Resources", "All Resources", "% risk-score"} -} - -func generateFooter(prettyPrinter *PrettyPrinter) []string { - // Control name | # failed resources | all resources | % success - row := []string{} - row = append(row, "Resource Summary") //fmt.Sprintf(""%d", numControlers")) - row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalFailed)) - row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalWarning)) - row = append(row, fmt.Sprintf("%d", prettyPrinter.frameworkSummary.TotalResources)) - row = append(row, fmt.Sprintf("%.2f%s", prettyPrinter.frameworkSummary.RiskScore, "%")) - - return row -} -func (prettyPrinter *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) { - // For control scan framework will be nil - prettyPrinter.printFramework(frameworksNames, frameworkScores) - - summaryTable := tablewriter.NewWriter(prettyPrinter.writer) - summaryTable.SetAutoWrapText(false) - summaryTable.SetHeader(generateHeader()) - summaryTable.SetHeaderLine(true) - alignments := []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER} - summaryTable.SetColumnAlignment(alignments) - - for i := 0; i < len(prettyPrinter.sortedControlNames); i++ { - controlSummary := prettyPrinter.summary[prettyPrinter.sortedControlNames[i]] - summaryTable.Append(generateRow(prettyPrinter.sortedControlNames[i], controlSummary)) - } - - summaryTable.SetFooter(generateFooter(prettyPrinter)) - - // summaryTable.SetFooter(generateFooter()) - summaryTable.Render() -} - -func (prettyPrinter *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) { - if len(frameworksNames) == 1 { - if frameworksNames[0] != "" { - cautils.InfoTextDisplay(prettyPrinter.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0])) - } - } else if len(frameworksNames) > 1 { - p := "FRAMEWORKS: " - for i := 0; i < len(frameworksNames)-1; i++ { - p += fmt.Sprintf("%s (risk: %.2f), ", frameworksNames[i], frameworkScores[i]) - } - p += fmt.Sprintf("%s (risk: %.2f)\n", frameworksNames[len(frameworksNames)-1], frameworkScores[len(frameworkScores)-1]) - cautils.InfoTextDisplay(prettyPrinter.writer, p) - } -} - -func (prettyPrinter *PrettyPrinter) getSortedControlsNames() []string { - controlNames := make([]string, 0, len(prettyPrinter.summary)) - for k := range prettyPrinter.summary { - controlNames = append(controlNames, k) - } - sort.Strings(controlNames) - return controlNames -} -func getControlURL(controlID string) string { - return fmt.Sprintf("https://hub.armo.cloud/docs/%s", strings.ToLower(controlID)) -} diff --git a/resultshandling/printer/v1/printresults.go b/resultshandling/printer/v1/printresults.go deleted file mode 100644 index 043020dc..00000000 --- a/resultshandling/printer/v1/printresults.go +++ /dev/null @@ -1,3 +0,0 @@ -package v1 - -var INDENT = " " diff --git a/resultshandling/printer/v1/silentprinter.go b/resultshandling/printer/v1/silentprinter.go deleted file mode 100644 index 37476b59..00000000 --- a/resultshandling/printer/v1/silentprinter.go +++ /dev/null @@ -1,11 +0,0 @@ -package v1 - -import ( - "github.com/armosec/kubescape/cautils" -) - -type SilentPrinter struct { -} - -func (silentPrinter *SilentPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { -} diff --git a/resultshandling/printer/v1/summary.go b/resultshandling/printer/v1/summary.go deleted file mode 100644 index 18eeed58..00000000 --- a/resultshandling/printer/v1/summary.go +++ /dev/null @@ -1,54 +0,0 @@ -package v1 - -import ( - "fmt" - - "github.com/armosec/k8s-interface/workloadinterface" - "github.com/armosec/opa-utils/reporthandling" -) - -type Summary map[string]ResultSummary - -func NewSummary() Summary { - return make(map[string]ResultSummary) -} - -type ResultSummary struct { - ID string - RiskScore float32 - TotalResources int - TotalFailed int - TotalWarning int - Description string - Remediation string - Framework []string - ListInputKinds []string - FailedWorkloads map[string][]WorkloadSummary // :[] - ExcludedWorkloads map[string][]WorkloadSummary // :[] - PassedWorkloads map[string][]WorkloadSummary // :[] -} - -type WorkloadSummary struct { - resource workloadinterface.IMetadata - status string -} - -func (controlSummary *ResultSummary) ToSlice() []string { - s := []string{} - s = append(s, fmt.Sprintf("%d", controlSummary.TotalFailed)) - s = append(s, fmt.Sprintf("%d", controlSummary.TotalWarning)) - s = append(s, fmt.Sprintf("%d", controlSummary.TotalResources)) - return s -} - -func workloadSummaryFailed(workloadSummary *WorkloadSummary) bool { - return workloadSummary.status == reporthandling.StatusFailed -} - -func workloadSummaryExclude(workloadSummary *WorkloadSummary) bool { - return workloadSummary.status == reporthandling.StatusWarning -} - -func workloadSummaryPassed(workloadSummary *WorkloadSummary) bool { - return workloadSummary.status == reporthandling.StatusPassed -} diff --git a/resultshandling/printer/v1/summeryhelpers.go b/resultshandling/printer/v1/summeryhelpers.go deleted file mode 100644 index cff9c74d..00000000 --- a/resultshandling/printer/v1/summeryhelpers.go +++ /dev/null @@ -1,85 +0,0 @@ -package v1 - -import ( - "github.com/armosec/k8s-interface/k8sinterface" - "github.com/armosec/k8s-interface/workloadinterface" - "github.com/armosec/opa-utils/objectsenvelopes" - "github.com/armosec/opa-utils/reporthandling" -) - -// Group workloads by namespace - return {"namespace": <[]WorkloadSummary>} -func groupByNamespaceOrKind(resources []WorkloadSummary, status func(workloadSummary *WorkloadSummary) bool) map[string][]WorkloadSummary { - mapResources := make(map[string][]WorkloadSummary) - for i := range resources { - if !status(&resources[i]) { - continue - } - t := resources[i].resource.GetObjectType() - if t == objectsenvelopes.TypeRegoResponseVectorObject && !isKindToBeGrouped(resources[i].resource.GetKind()) { - t = workloadinterface.TypeWorkloadObject - } - switch t { // TODO - find a better way to defind the groups - case workloadinterface.TypeWorkloadObject: - ns := "" - if resources[i].resource.GetNamespace() != "" { - ns = "Namespace " + resources[i].resource.GetNamespace() - } - if r, ok := mapResources[ns]; ok { - r = append(r, resources[i]) - mapResources[ns] = r - } else { - mapResources[ns] = []WorkloadSummary{resources[i]} - } - case objectsenvelopes.TypeRegoResponseVectorObject: - group := resources[i].resource.GetKind() + "s" - if r, ok := mapResources[group]; ok { - r = append(r, resources[i]) - mapResources[group] = r - } else { - mapResources[group] = []WorkloadSummary{resources[i]} - } - default: - group, _ := k8sinterface.SplitApiVersion(resources[i].resource.GetApiVersion()) - if r, ok := mapResources[group]; ok { - r = append(r, resources[i]) - mapResources[group] = r - } else { - mapResources[group] = []WorkloadSummary{resources[i]} - } - } - } - return mapResources -} - -func isKindToBeGrouped(kind string) bool { - if kind == "Group" || kind == "User" { - return true - } - return false -} - -func listResultSummary(ruleReports []reporthandling.RuleReport, allResources map[string]workloadinterface.IMetadata) []WorkloadSummary { - workloadsSummary := []WorkloadSummary{} - - for c := range ruleReports { - resourcesIDs := ruleReports[c].ListResourcesIDs() - workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetFailedResources(), reporthandling.StatusFailed)...) - workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetWarningResources(), reporthandling.StatusWarning)...) - workloadsSummary = append(workloadsSummary, newListWorkloadsSummary(allResources, resourcesIDs.GetPassedResources(), reporthandling.StatusPassed)...) - } - return workloadsSummary -} - -func newListWorkloadsSummary(allResources map[string]workloadinterface.IMetadata, resourcesIDs []string, status string) []WorkloadSummary { - workloadsSummary := []WorkloadSummary{} - - for _, i := range resourcesIDs { - if r, ok := allResources[i]; ok { - workloadsSummary = append(workloadsSummary, WorkloadSummary{ - resource: r, - status: status, - }) - } - } - return workloadsSummary -} diff --git a/resultshandling/printer/v2/junit.go b/resultshandling/printer/v2/junit.go index 84cbd757..f1b0317f 100644 --- a/resultshandling/printer/v2/junit.go +++ b/resultshandling/printer/v2/junit.go @@ -5,18 +5,22 @@ import ( "fmt" "os" + "github.com/armosec/armoapi-go/armotypes" "github.com/armosec/kubescape/cautils" "github.com/armosec/kubescape/cautils/logger" + "github.com/armosec/kubescape/cautils/logger/helpers" "github.com/armosec/kubescape/resultshandling/printer" - reporthandlingv2 "github.com/armosec/opa-utils/reporthandling/v2" ) type JunitPrinter struct { - writer *os.File + writer *os.File + verbose bool } -func NewJunitPrinter() *JunitPrinter { - return &JunitPrinter{} +func NewJunitPrinter(verbose bool) *JunitPrinter { + return &JunitPrinter{ + verbose: verbose, + } } func (junitPrinter *JunitPrinter) SetWriter(outputFile string) { @@ -32,97 +36,89 @@ func (junitPrinter *JunitPrinter) FinalizeData(opaSessionObj *cautils.OPASession } func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { - junitResult, err := convertPostureReportToJunitResult(opaSessionObj.Report) + junitResult, err := junitPrinter.convertPostureReportToJunitResult(opaSessionObj) if err != nil { - logger.L().Fatal("failed to convert posture report object") + logger.L().Fatal("failed to build xml result object", helpers.Error(err)) } postureReportStr, err := xml.Marshal(junitResult) if err != nil { - logger.L().Fatal("failed to convert posture report object") + logger.L().Fatal("failed to Marshal xml result object", helpers.Error(err)) } junitPrinter.writer.Write(postureReportStr) } type JUnitTestSuites struct { - XMLName xml.Name `xml:"testsuites"` - Suites []JUnitTestSuite `xml:"testsuite"` -} - -// JUnitTestSuite is a single JUnit test suite which may contain many -// testcases. -type JUnitTestSuite struct { - XMLName xml.Name `xml:"testsuite"` - Tests int `xml:"tests,attr"` - Time string `xml:"time,attr"` - Name string `xml:"name,attr"` - Resources int `xml:"resources,attr"` - Excluded int `xml:"excluded,attr"` - Failed int `xml:"filed,attr"` - Properties []JUnitProperty `xml:"properties>property,omitempty"` - TestCases []JUnitTestCase `xml:"testcase"` + XMLName xml.Name `xml:"testsuite"` + Suites []JUnitTestCase `xml:"testsuites"` + RiskScore float32 `xml:"riskScore,attr"` // test risk score + Time string `xml:"time,attr"` // scanning time + Controls int `xml:"tests,attr"` // number of controls } // JUnitTestCase is a single test case with its result. -type JUnitTestCase struct { - XMLName xml.Name `xml:"testcase"` - Classname string `xml:"classname,attr"` - Name string `xml:"name,attr"` - Time string `xml:"time,attr"` - Resources int `xml:"resources,attr"` - Excluded int `xml:"excluded,attr"` - Failed int `xml:"filed,attr"` - SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` - Failure *JUnitFailure `xml:"failure,omitempty"` +type JUnitTestCase struct { // Control + XMLName xml.Name `xml:"testcase"` + RiskScore float32 `xml:"riskScore,attr"` + Status string `xml:"status,attr"` + Name string `xml:"name,attr"` + AllResources int `xml:"allResources,attr"` + Excluded int `xml:"excludedResources,attr"` + Failed int `xml:"filedResources,attr"` + Resources []JUnitResource `xml:"resources"` } -// JUnitSkipMessage contains the reason why a testcase was skipped. -type JUnitSkipMessage struct { - Message string `xml:"message,attr"` +type JUnitResource struct { // Single resource + Name string `xml:"name,attr"` + Namespace string `xml:"namespace,attr"` + Kind string `xml:"kind,attr"` + ApiVersion string `xml:"apiVersion,attr"` + FailedPaths []armotypes.PosturePaths `xml:"jsonPaths"` } -// JUnitProperty represents a key/value pair used to define properties. -type JUnitProperty struct { - Name string `xml:"name,attr"` - Value string `xml:"value,attr"` -} - -// JUnitFailure contains data related to a failed test. -type JUnitFailure struct { - Message string `xml:"message,attr"` - Type string `xml:"type,attr"` - Contents string `xml:",chardata"` -} - -func convertPostureReportToJunitResult(postureResult *reporthandlingv2.PostureReport) (*JUnitTestSuites, error) { - juResult := JUnitTestSuites{XMLName: xml.Name{Local: "Kubescape scan results"}} - for _, framework := range postureResult.ListFrameworks().All() { - suite := JUnitTestSuite{ - Name: framework.GetName(), - Resources: framework.NumberOfResources().All(), - Excluded: framework.NumberOfResources().Excluded(), - Failed: framework.NumberOfResources().Failed(), - } - for _, controlReports := range postureResult.ListControls().All() { - suite.Tests = suite.Tests + 1 - testCase := JUnitTestCase{} - testCase.Name = controlReports.GetName() - testCase.Classname = "Kubescape" - testCase.Time = postureResult.ReportGenerationTime.String() - // if 0 < len(controlReports.RuleReports[0].RuleResponses) { - - // testCase.Resources = controlReports.NumberOfResources().All() - // testCase.Excluded = controlReports.NumberOfResources().Excluded() - // testCase.Failed = controlReports.NumberOfResources().Failed() - // failure := JUnitFailure{} - // failure.Message = fmt.Sprintf("%d resources failed", testCase.Failed) - // for _, ruleResponses := range controlReports.RuleReports[0].RuleResponses { - // failure.Contents = fmt.Sprintf("%s\n%s", failure.Contents, ruleResponses.AlertMessage) - // } - // testCase.Failure = &failure - // } - suite.TestCases = append(suite.TestCases, testCase) - } - juResult.Suites = append(juResult.Suites, suite) +func (junitPrinter *JunitPrinter) convertPostureReportToJunitResult(results *cautils.OPASessionObj) (*JUnitTestSuites, error) { + juResult := JUnitTestSuites{ + XMLName: xml.Name{ + Local: "Kubescape scan results", + }, + RiskScore: results.Report.SummaryDetails.Score, + Time: results.Report.GetTimestamp().String(), + Controls: len(results.Report.ListControls().All()), } + + // controls + for _, controlReports := range results.Report.ListControls().All() { + testCase := JUnitTestCase{} + testCase.Name = controlReports.GetName() + testCase.Status = string(controlReports.GetStatus().Status()) + + // resources + testCase.AllResources = controlReports.NumberOfResources().All() + testCase.Excluded = controlReports.NumberOfResources().Excluded() + testCase.Failed = controlReports.NumberOfResources().Failed() + + var jUnitResources []JUnitResource + for _, resourceID := range controlReports.ListResourcesIDs().All() { + if !junitPrinter.verbose { + continue + } + jUnitResource := JUnitResource{} + if resource, ok := results.AllResources[resourceID]; ok { + jUnitResource.Name = resource.GetName() + jUnitResource.Namespace = resource.GetNamespace() + jUnitResource.Kind = resource.GetKind() + jUnitResource.ApiVersion = resource.GetApiVersion() + } + if result, ok := results.ResourcesResult[resourceID]; ok { + rules := result.ListRulesOfControl("", controlReports.GetName()) + for _, rule := range rules { + jUnitResource.FailedPaths = append(jUnitResource.FailedPaths, rule.Paths...) + } + } + jUnitResources = append(jUnitResources, jUnitResource) + } + testCase.Resources = jUnitResources + juResult.Suites = append(juResult.Suites, testCase) + } + return &juResult, nil } diff --git a/resultshandling/printer/v2/resourcemapping/jsonprinter.go b/resultshandling/printer/v2/resourcemapping/jsonprinter.go deleted file mode 100644 index 38194cb5..00000000 --- a/resultshandling/printer/v2/resourcemapping/jsonprinter.go +++ /dev/null @@ -1,41 +0,0 @@ -package resourcemapping - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/armosec/kubescape/cautils" - "github.com/armosec/kubescape/cautils/logger" - "github.com/armosec/kubescape/resultshandling/printer" -) - -type JsonPrinter struct { - writer *os.File -} - -func NewJsonPrinter() *JsonPrinter { - return &JsonPrinter{} -} - -func (jsonPrinter *JsonPrinter) SetWriter(outputFile string) { - jsonPrinter.writer = printer.GetWriter(outputFile) -} - -func (jsonPrinter *JsonPrinter) Score(score float32) { - fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score)) -} - -func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { - - postureReportStr, err := json.Marshal(opaSessionObj.Report) - - if err != nil { - logger.L().Fatal("failed to convert posture report object") - } - jsonPrinter.writer.Write(postureReportStr) -} - -func (jsonPrinter *JsonPrinter) FinalizeData(opaSessionObj *cautils.OPASessionObj) { - // finalizeReport(opaSessionObj) -} diff --git a/resultshandling/results.go b/resultshandling/results.go index f6f4917d..e5ec8ec5 100644 --- a/resultshandling/results.go +++ b/resultshandling/results.go @@ -59,7 +59,7 @@ func NewPrinter(printFormat, outputVersion string, verboseMode bool) printer.IPr case printer.JsonFormat: return printerv1.NewJsonPrinter() case printer.JunitResultFormat: - return printerv1.NewJunitPrinter() + return printerv2.NewJunitPrinter(verboseMode) case printer.PrometheusFormat: return printerv1.NewPrometheusPrinter(verboseMode) case printer.PdfFormat: From 544a19906e149086341235818794db26f4c7cc2a Mon Sep 17 00:00:00 2001 From: dwertent Date: Sun, 27 Feb 2022 17:29:42 +0200 Subject: [PATCH 2/2] update junit --- resultshandling/printer/v2/junit.go | 124 ++++++++++++-------- resultshandling/printer/v2/resourcetable.go | 27 +++-- 2 files changed, 92 insertions(+), 59 deletions(-) diff --git a/resultshandling/printer/v2/junit.go b/resultshandling/printer/v2/junit.go index f1b0317f..987a4f79 100644 --- a/resultshandling/printer/v2/junit.go +++ b/resultshandling/printer/v2/junit.go @@ -10,6 +10,7 @@ import ( "github.com/armosec/kubescape/cautils/logger" "github.com/armosec/kubescape/cautils/logger/helpers" "github.com/armosec/kubescape/resultshandling/printer" + "github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary" ) type JunitPrinter struct { @@ -17,6 +18,44 @@ type JunitPrinter struct { verbose bool } +type JUnitTestSuites struct { + XMLName xml.Name `xml:"testsuite"` + Suites []JUnitTestCase `xml:"testsuites"` + Frameworks []JUnitFrameworks `xml:"framework"` + RiskScore float32 `xml:"riskScore,attr"` // test risk score + Time string `xml:"time,attr"` // scanning time + Controls int `xml:"testcases,attr"` // number of controls +} + +type JUnitFrameworks struct { // Frameworks + Name string `xml:"name,attr"` + RiskScore float32 `xml:"riskscore,attr"` + Status string `xml:"status,attr"` +} + +// JUnitTestCase is a single test case with its result. +type JUnitTestCase struct { // Control + XMLName xml.Name `xml:"testcase"` + Name string `xml:"name,attr"` + ID string `xml:"id,attr"` + Url string `xml:"url,attr"` + RiskScore float32 `xml:"riskScore,attr"` + Status string `xml:"status,attr"` + Info string `xml:"info,attr"` + AllResources int `xml:"allResources,attr"` + Excluded int `xml:"excludedResources,attr"` + Failed int `xml:"filedResources,attr"` + Resources []JUnitResource `xml:"resource"` +} + +type JUnitResource struct { // Single resource + Name string `xml:"name,attr"` + Namespace string `xml:"namespace,attr"` + Kind string `xml:"kind,attr"` + ApiVersion string `xml:"apiVersion,attr"` + FailedPaths []armotypes.PosturePaths `xml:"jsonpath"` +} + func NewJunitPrinter(verbose bool) *JunitPrinter { return &JunitPrinter{ verbose: verbose, @@ -47,73 +86,66 @@ func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionO junitPrinter.writer.Write(postureReportStr) } -type JUnitTestSuites struct { - XMLName xml.Name `xml:"testsuite"` - Suites []JUnitTestCase `xml:"testsuites"` - RiskScore float32 `xml:"riskScore,attr"` // test risk score - Time string `xml:"time,attr"` // scanning time - Controls int `xml:"tests,attr"` // number of controls -} - -// JUnitTestCase is a single test case with its result. -type JUnitTestCase struct { // Control - XMLName xml.Name `xml:"testcase"` - RiskScore float32 `xml:"riskScore,attr"` - Status string `xml:"status,attr"` - Name string `xml:"name,attr"` - AllResources int `xml:"allResources,attr"` - Excluded int `xml:"excludedResources,attr"` - Failed int `xml:"filedResources,attr"` - Resources []JUnitResource `xml:"resources"` -} - -type JUnitResource struct { // Single resource - Name string `xml:"name,attr"` - Namespace string `xml:"namespace,attr"` - Kind string `xml:"kind,attr"` - ApiVersion string `xml:"apiVersion,attr"` - FailedPaths []armotypes.PosturePaths `xml:"jsonPaths"` -} - func (junitPrinter *JunitPrinter) convertPostureReportToJunitResult(results *cautils.OPASessionObj) (*JUnitTestSuites, error) { juResult := JUnitTestSuites{ XMLName: xml.Name{ Local: "Kubescape scan results", }, - RiskScore: results.Report.SummaryDetails.Score, - Time: results.Report.GetTimestamp().String(), - Controls: len(results.Report.ListControls().All()), + RiskScore: results.Report.SummaryDetails.Score, + Time: results.Report.GetTimestamp().String(), + Controls: len(results.Report.ListControls().All()), + Frameworks: []JUnitFrameworks{}, + } + + // Frameworks + for _, frameworksReports := range results.Report.ListFrameworks().All() { + fw := JUnitFrameworks{} + fw.Name = frameworksReports.GetName() + fw.RiskScore = frameworksReports.GetScore() + fw.Status = string(frameworksReports.GetStatus().Status()) + juResult.Frameworks = append(juResult.Frameworks, fw) } // controls - for _, controlReports := range results.Report.ListControls().All() { + for _, controlIDs := range results.Report.ListControlsIDs().All() { + controlReport := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, controlIDs) + + // control data testCase := JUnitTestCase{} - testCase.Name = controlReports.GetName() - testCase.Status = string(controlReports.GetStatus().Status()) + testCase.Name = controlReport.GetName() + testCase.ID = controlReport.GetID() + testCase.Url = getControlURL(controlReport.GetID()) + testCase.Name = controlReport.GetName() + testCase.Status = string(controlReport.GetStatus().Status()) + + // resources counters + testCase.AllResources = controlReport.NumberOfResources().All() + testCase.Excluded = controlReport.NumberOfResources().Excluded() + testCase.Failed = controlReport.NumberOfResources().Failed() // resources - testCase.AllResources = controlReports.NumberOfResources().All() - testCase.Excluded = controlReports.NumberOfResources().Excluded() - testCase.Failed = controlReports.NumberOfResources().Failed() - var jUnitResources []JUnitResource - for _, resourceID := range controlReports.ListResourcesIDs().All() { - if !junitPrinter.verbose { + for _, resourceID := range controlReport.ListResourcesIDs().All() { + result, ok := results.ResourcesResult[resourceID] + if !ok { continue } + if result.GetStatus(nil).IsPassed() && !junitPrinter.verbose { // add passed resources only in verbose mode + continue + } + jUnitResource := JUnitResource{} + rules := result.ListRulesOfControl(controlReport.GetID(), "") + for _, rule := range rules { + jUnitResource.FailedPaths = append(jUnitResource.FailedPaths, rule.Paths...) + } if resource, ok := results.AllResources[resourceID]; ok { jUnitResource.Name = resource.GetName() jUnitResource.Namespace = resource.GetNamespace() jUnitResource.Kind = resource.GetKind() jUnitResource.ApiVersion = resource.GetApiVersion() } - if result, ok := results.ResourcesResult[resourceID]; ok { - rules := result.ListRulesOfControl("", controlReports.GetName()) - for _, rule := range rules { - jUnitResource.FailedPaths = append(jUnitResource.FailedPaths, rule.Paths...) - } - } + jUnitResources = append(jUnitResources, jUnitResource) } testCase.Resources = jUnitResources diff --git a/resultshandling/printer/v2/resourcetable.go b/resultshandling/printer/v2/resourcetable.go index cc291e8e..843963bf 100644 --- a/resultshandling/printer/v2/resourcetable.go +++ b/resultshandling/printer/v2/resourcetable.go @@ -2,6 +2,7 @@ package v2 import ( "fmt" + "sort" "strings" "github.com/armosec/k8s-interface/workloadinterface" @@ -21,7 +22,7 @@ func (prettyPrinter *PrettyPrinter) resourceTable(results map[string]resourcesre summaryTable.SetRowLine(true) // summaryTable.SetFooter([]string{"", "", "Total", "", "$146.93"}) // For control scan framework will be nil - data := [][]string{} + data := Matrix{} for i := range results { resource, ok := allResources[i] if !ok { @@ -32,7 +33,7 @@ func (prettyPrinter *PrettyPrinter) resourceTable(results map[string]resourcesre data = append(data, raw...) } } - sortTable(data) + sort.Sort(data) summaryTable.AppendBulk(data) summaryTable.Render() @@ -78,18 +79,18 @@ func generateResourceHeader() []string { return []string{"Control", "Namespace", "Kind/Name", "Statues"} } -func sortTable(data [][]string) { +type Matrix [][]string - for j := len(data[0]) - 1; j >= 0; j-- { - for k := 0; k < len(data)-2; { - if data[k][j] > data[k+1][j] { - tmp := data[k] - data[k] = data[k+1] - data[k+1] = tmp - k = 0 - } else { - k++ - } +func (a Matrix) Len() int { return len(a) } +func (a Matrix) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a Matrix) Less(i, j int) bool { + l := len(a[i]) + for k := 0; k < l; k++ { + if a[i][k] < a[j][k] { + return true + } else if a[i][k] > a[j][k] { + return false } } + return true }