diff --git a/core/core/scan.go b/core/core/scan.go index 1af6a847..46b065ba 100644 --- a/core/core/scan.go +++ b/core/core/scan.go @@ -15,7 +15,6 @@ import ( "github.com/kubescape/kubescape/v2/core/pkg/opaprocessor" "github.com/kubescape/kubescape/v2/core/pkg/policyhandler" "github.com/kubescape/kubescape/v2/core/pkg/resourcehandler" - "github.com/kubescape/kubescape/v2/core/pkg/resourcesprioritization" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/reporter" @@ -153,11 +152,15 @@ func (ks *Kubescape) Scan(scanInfo *cautils.ScanInfo) (*resultshandling.ResultsH return resultsHandling, fmt.Errorf("%w", err) } - // ======================== prioritization =================== - priotizationHandler := resourcesprioritization.NewResourcesPrioritizationHandler(true) - if err := priotizationHandler.PrioritizeResources(scanData); err != nil { - return resultsHandling, fmt.Errorf("%w", err) - } + /* + + // ======================== prioritization =================== + priotizationHandler := resourcesprioritization.NewResourcesPrioritizationHandler(true) + if err := priotizationHandler.PrioritizeResources(scanData); err != nil { + return resultsHandling, fmt.Errorf("%w", err) + } + + */ // ========================= results handling ===================== resultsHandling.SetData(scanData) diff --git a/core/pkg/resultshandling/printer/v2/htmlprinter.go b/core/pkg/resultshandling/printer/v2/htmlprinter.go index 5e0dfe13..958c6e25 100644 --- a/core/pkg/resultshandling/printer/v2/htmlprinter.go +++ b/core/pkg/resultshandling/printer/v2/htmlprinter.go @@ -131,7 +131,7 @@ func buildResourceControlResult(resourceControl resourcesresults.ResourceAssocia ctlSeverity := apis.ControlSeverityToString(control.GetScoreFactor()) ctlName := resourceControl.GetName() ctlURL := resourceControl.GetID() - failedPaths := failedPathsToString(&resourceControl) + failedPaths := append(failedPathsToString(&resourceControl), fixPathsToString(&resourceControl)...) return ResourceControlResult{ctlSeverity, ctlName, ctlURL, failedPaths} } diff --git a/core/pkg/resultshandling/printer/v2/junit.go b/core/pkg/resultshandling/printer/v2/junit.go index a7a53d57..cbc2842b 100644 --- a/core/pkg/resultshandling/printer/v2/junit.go +++ b/core/pkg/resultshandling/printer/v2/junit.go @@ -4,7 +4,6 @@ import ( "encoding/xml" "fmt" "os" - "sort" "strings" logger "github.com/kubescape/go-logger" @@ -12,8 +11,9 @@ import ( "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/kubescape/v2/core/cautils" "github.com/kubescape/kubescape/v2/core/pkg/resultshandling/printer" + "github.com/kubescape/opa-utils/reporthandling/apis" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" - "github.com/kubescape/opa-utils/shared" + "github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults" ) /* @@ -55,6 +55,7 @@ type JUnitTestSuite struct { Skipped string `xml:"skipped,attr"` // The total number of skipped tests Time string `xml:"time,attr"` // Time taken (in seconds) to execute the tests in the suite Timestamp string `xml:"timestamp,attr"` // when the test was executed in ISO 8601 format (2014-01-21T16:17:18) + File string `xml:"file,attr"` // The file be tested Properties []JUnitProperty `xml:"properties>property,omitempty"` TestCases []JUnitTestCase `xml:"testcase"` } @@ -88,6 +89,11 @@ type JUnitFailure struct { Contents string `xml:",chardata"` } +const ( + lineSeparator = "\n===================================================================================================================\n\n" + testCaseTypeResources = "Resources" +) + func NewJunitPrinter(verbose bool) *JunitPrinter { return &JunitPrinter{ verbose: verbose, @@ -118,96 +124,119 @@ func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionO func testsSuites(results *cautils.OPASessionObj) *JUnitTestSuites { return &JUnitTestSuites{ Suites: listTestsSuite(results), - Tests: results.Report.SummaryDetails.NumberOfControls().All(), + Tests: results.Report.SummaryDetails.NumberOfResources().All(), Name: "Kubescape Scanning", - Failures: results.Report.SummaryDetails.NumberOfControls().Failed(), + Failures: results.Report.SummaryDetails.NumberOfResources().Failed(), } } + +// aggregate resources source to a list of resources results +func sourceToResourcesResults(results *cautils.OPASessionObj) map[string][]resourcesresults.Result { + resourceResults := make(map[string][]resourcesresults.Result) + for i := range results.ResourceSource { + if r, ok := results.ResourcesResult[i]; ok { + if _, ok := resourceResults[results.ResourceSource[i].RelativePath]; !ok { + resourceResults[results.ResourceSource[i].RelativePath] = []resourcesresults.Result{} + } + resourceResults[results.ResourceSource[i].RelativePath] = append(resourceResults[results.ResourceSource[i].RelativePath], r) + } + } + return resourceResults +} + +// listTestsSuite returns a list of testsuites func listTestsSuite(results *cautils.OPASessionObj) []JUnitTestSuite { var testSuites []JUnitTestSuite - + resourceResults := sourceToResourcesResults(results) + counter := 0 // control scan - if len(results.Report.SummaryDetails.ListFrameworks()) == 0 { + for path, resourcesResult := range resourceResults { testSuite := JUnitTestSuite{} - testSuite.Failures = results.Report.SummaryDetails.NumberOfControls().Failed() testSuite.Timestamp = results.Report.ReportGenerationTime.String() - testSuite.ID = 0 - testSuite.Name = "kubescape" - testSuite.Properties = properties(results.Report.SummaryDetails.Score) - testSuite.TestCases = testsCases(results, &results.Report.SummaryDetails.Controls, "Kubescape") - testSuites = append(testSuites, testSuite) - return testSuites - } - - for i, f := range results.Report.SummaryDetails.Frameworks { - testSuite := JUnitTestSuite{} - testSuite.Failures = f.NumberOfControls().Failed() - testSuite.Timestamp = results.Report.ReportGenerationTime.String() - testSuite.ID = i - testSuite.Name = f.Name - testSuite.Properties = properties(f.Score) - testSuite.TestCases = testsCases(results, f.GetControls(), f.GetName()) - testSuites = append(testSuites, testSuite) + testSuite.ID = counter + counter++ + testSuite.File = path + testSuite.TestCases = testsCases(results, resourcesResult) + if len(testSuite.TestCases) > 0 { + testSuites = append(testSuites, testSuite) + } } return testSuites } -func testsCases(results *cautils.OPASessionObj, controls reportsummary.IControlsSummaries, classname string) []JUnitTestCase { - var testCases []JUnitTestCase - iter := controls.ListControlsIDs().All() - for iter.HasNext() { - cID := iter.Next() - testCase := JUnitTestCase{} - control := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, cID) - - testCase.Name = control.GetName() - testCase.Classname = classname - testCase.Status = string(control.GetStatus().Status()) - - if control.GetStatus().IsFailed() { - resources := map[string]interface{}{} - resourceIDs := control.ListResourcesIDs().Failed() - for j := range resourceIDs { - resource := results.AllResources[resourceIDs[j]] - resources[resourceToString(resource)] = nil +func failedControlsToFailureMessage(results *cautils.OPASessionObj, controls []resourcesresults.ResourceAssociatedControl, severityCounter []int) string { + msg := "" + for _, c := range controls { + control := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, c.GetID()) + if c.GetStatus(nil).IsFailed() { + msg += fmt.Sprintf("Test: %s\n", control.GetName()) + msg += fmt.Sprintf("Severity: %s\n", apis.ControlSeverityToString(control.GetScoreFactor())) + msg += fmt.Sprintf("Remediation: %s\n", control.GetRemediation()) + msg += fmt.Sprintf("Link: %s\n", getControlLink(control.GetID())) + if failedPaths := failedPathsToString(&c); len(failedPaths) > 0 { + msg += fmt.Sprintf("Failed paths: \n - %s\n", strings.Join(failedPaths, "\n - ")) } - resourcesStr := shared.MapStringToSlice(resources) - sort.Strings(resourcesStr) - testCaseFailure := JUnitFailure{} - testCaseFailure.Type = "Control" - // testCaseFailure.Contents = - testCaseFailure.Message = fmt.Sprintf("Remediation: %s\nMore details: %s\n\n%s", control.GetRemediation(), getControlLink(control.GetID()), strings.Join(resourcesStr, "\n")) - - testCase.Failure = &testCaseFailure - } else if control.GetStatus().IsSkipped() { - testCase.SkipMessage = &JUnitSkipMessage{ - Message: "", // TODO - fill after statusInfo is supported + if fixPaths := fixPathsToString(&c); len(fixPaths) > 0 { + msg += fmt.Sprintf("Available fix: \n - %s\n", strings.Join(fixPaths, "\n - ")) } + msg += "\n" + severityCounter[apis.ControlSeverityToInt(control.GetScoreFactor())] += 1 } + } + return msg +} + +// Every testCase includes a file (even if the file contains several resources) +func testsCases(results *cautils.OPASessionObj, resourcesResult []resourcesresults.Result) []JUnitTestCase { + var testCases []JUnitTestCase + testCase := JUnitTestCase{} + testCaseFailure := JUnitFailure{} + testCaseFailure.Type = testCaseTypeResources + message := "" + + // severityCounter represents the severities, 0: Unknown, 1: Low, 2: Medium, 3: High, 4: Critical + severityCounter := make([]int, apis.NumberOfSeverities, apis.NumberOfSeverities) + + for i := range resourcesResult { + message += lineSeparator + if failedControls := failedControlsToFailureMessage(results, resourcesResult[i].ListControls(), severityCounter); failedControls != "" { + message += fmt.Sprintf("Resource: %s\n\n%s", resourceNameToString(results.AllResources[resourcesResult[i].GetResourceID()]), failedControls) + } + } + testCaseFailure.Message += fmt.Sprintf("%s\n%s", getSummaryMessage(severityCounter), message) + + testCase.Failure = &testCaseFailure + if testCase.Failure.Message != "" { testCases = append(testCases, testCase) } + return testCases } -func resourceToString(resource workloadinterface.IMetadata) string { - sep := "; " - s := "" - s += fmt.Sprintf("apiVersion: %s", resource.GetApiVersion()) + sep - s += fmt.Sprintf("kind: %s", resource.GetKind()) + sep - if resource.GetNamespace() != "" { - s += fmt.Sprintf("namespace: %s", resource.GetNamespace()) + sep +func getSummaryMessage(severityCounter []int) string { + total := 0 + severities := "" + for i, count := range severityCounter { + if apis.SeverityNumberToString(i) == apis.SeverityNumberToString(apis.SeverityUnknown) { + continue + } + severities += fmt.Sprintf("%s: %d, ", apis.SeverityNumberToString(i), count) + total += count } - s += fmt.Sprintf("name: %s", resource.GetName()) - return s + if len(severities) == 0 { + return "" + } + return fmt.Sprintf("Total: %d (%s)", total, severities[:len(severities)-2]) } -func properties(riskScore float32) []JUnitProperty { - return []JUnitProperty{ - { - Name: "riskScore", - Value: fmt.Sprintf("%.2f", riskScore), - }, +func resourceNameToString(resource workloadinterface.IMetadata) string { + s := "" + s += fmt.Sprintf("kind=%s/", resource.GetKind()) + if resource.GetNamespace() != "" { + s += fmt.Sprintf("namespace=%s/", resource.GetNamespace()) } + s += fmt.Sprintf("name=%s", resource.GetName()) + return s } diff --git a/core/pkg/resultshandling/printer/v2/resourcetable.go b/core/pkg/resultshandling/printer/v2/resourcetable.go index 28951ec0..27ffa3d1 100644 --- a/core/pkg/resultshandling/printer/v2/resourcetable.go +++ b/core/pkg/resultshandling/printer/v2/resourcetable.go @@ -74,7 +74,7 @@ func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, } row[resourceColumnURL] = fmt.Sprintf("https://hub.armosec.io/docs/%s", strings.ToLower(controls[i].GetID())) - row[resourceColumnPath] = strings.Join(failedPathsToString(&controls[i]), "\n") + row[resourceColumnPath] = strings.Join(append(failedPathsToString(&controls[i]), fixPathsToString(&controls[i])...), "\n") row[resourceColumnName] = controls[i].GetName() if c := summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, controls[i].GetName()); c != nil { @@ -120,6 +120,16 @@ func failedPathsToString(control *resourcesresults.ResourceAssociatedControl) [] if p := control.ResourceAssociatedRules[j].Paths[k].FailedPath; p != "" { paths = append(paths, p) } + } + } + return paths +} + +func fixPathsToString(control *resourcesresults.ResourceAssociatedControl) []string { + var paths []string + + for j := range control.ResourceAssociatedRules { + for k := range control.ResourceAssociatedRules[j].Paths { if p := control.ResourceAssociatedRules[j].Paths[k].FixPath.Path; p != "" { v := control.ResourceAssociatedRules[j].Paths[k].FixPath.Value paths = append(paths, fmt.Sprintf("%s=%s", p, v)) diff --git a/core/pkg/resultshandling/printer/v2/utils.go b/core/pkg/resultshandling/printer/v2/utils.go index e14c93e3..4933c09e 100644 --- a/core/pkg/resultshandling/printer/v2/utils.go +++ b/core/pkg/resultshandling/printer/v2/utils.go @@ -38,7 +38,7 @@ func finalizeResults(results []resourcesresults.Result, resourcesResult map[stri // Add prioritization information to the result if v, exist := prioritizedResources[resourceID]; exist { - results[index].PrioritizedResource = v + results[index].PrioritizedResource = &v } index++ } diff --git a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go index 8f8da4f5..809825ee 100644 --- a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go +++ b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go @@ -139,21 +139,24 @@ func (report *ReportEventReceiver) sendResources(host string, opaSessionObj *cau func (report *ReportEventReceiver) setResults(reportObj *reporthandlingv2.PostureReport, results map[string]resourcesresults.Result, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]reporthandling.Source, prioritizedResources map[string]prioritization.PrioritizedResource, counter, reportCounter *int, host string) error { for _, v := range results { - // set result.RawResource - resourceID := v.GetResourceID() - if _, ok := allResources[resourceID]; !ok { - return fmt.Errorf("expected to find raw resource object for '%s'", resourceID) - } - resource := reporthandling.NewResourceIMetadata(allResources[resourceID]) - if r, ok := resourcesSource[resourceID]; ok { - resource.SetSource(&r) - } - v.RawResource = *resource + /* - // set result.PrioritizedResource - if _, ok := prioritizedResources[resourceID]; ok { - v.PrioritizedResource = prioritizedResources[resourceID] - } + // set result.RawResource + resourceID := v.GetResourceID() + if _, ok := allResources[resourceID]; !ok { + return fmt.Errorf("expected to find raw resource object for '%s'", resourceID) + } + resource := reporthandling.NewResourceIMetadata(allResources[resourceID]) + if r, ok := resourcesSource[resourceID]; ok { + resource.SetSource(&r) + } + v.RawResource = resource + + // set result.PrioritizedResource + if resource, ok := prioritizedResources[resourceID]; ok { + v.PrioritizedResource = &resource + } + */ r, err := json.Marshal(v) if err != nil { @@ -184,10 +187,14 @@ func (report *ReportEventReceiver) setResults(reportObj *reporthandlingv2.Postur func (report *ReportEventReceiver) setResources(reportObj *reporthandlingv2.PostureReport, allResources map[string]workloadinterface.IMetadata, resourcesSource map[string]reporthandling.Source, results map[string]resourcesresults.Result, counter, reportCounter *int, host string) error { for resourceID, v := range allResources { - // process only resources which have no result because these resources will be sent on the result object - if _, hasResult := results[resourceID]; hasResult { - continue - } + /* + + // process only resources which have no result because these resources will be sent on the result object + if _, hasResult := results[resourceID]; hasResult { + continue + } + + */ resource := reporthandling.NewResourceIMetadata(v) if r, ok := resourcesSource[resourceID]; ok { diff --git a/go.mod b/go.mod index 0d805c75..e89e12ed 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/johnfercher/maroto v0.37.0 github.com/kubescape/go-logger v0.0.6 github.com/kubescape/k8s-interface v0.0.83 - github.com/kubescape/opa-utils v0.0.181 + github.com/kubescape/opa-utils v0.0.186 github.com/kubescape/rbac-utils v0.0.17 github.com/libgit2/git2go/v33 v33.0.9 github.com/mattn/go-isatty v0.0.14 diff --git a/go.sum b/go.sum index 83f970a4..07a20d66 100644 --- a/go.sum +++ b/go.sum @@ -839,8 +839,8 @@ github.com/kubescape/go-logger v0.0.6 h1:ynhAmwrz0O7Jtqq1CdmCZUrKveji25hVP+B/FAb github.com/kubescape/go-logger v0.0.6/go.mod h1:DnVWEvC90LFY1nNMaNo6nBVOcqkLMK3S0qzXP1fzRvI= github.com/kubescape/k8s-interface v0.0.83 h1:yQ1kWNZmKfBim/+NmxpPI/j7L9ASDq2h3mCNdmYgzqY= github.com/kubescape/k8s-interface v0.0.83/go.mod h1:ihX96yqar+xogHl45mFE8zT9DLI06iy7XQPAP+j5KJE= -github.com/kubescape/opa-utils v0.0.181 h1:At3/4+yS7NqIKYXRj4iwdLmrGOR6B6SyYMALme9axAI= -github.com/kubescape/opa-utils v0.0.181/go.mod h1:jC5QrhS6WiFj/tXP2/YSDBnFlGsYUQokDGbKwMBgMpw= +github.com/kubescape/opa-utils v0.0.186 h1:3T0lD3x1/hweXY/HeNM2I4h8Ugh9SacXPo1AaOPBrAs= +github.com/kubescape/opa-utils v0.0.186/go.mod h1:jC5QrhS6WiFj/tXP2/YSDBnFlGsYUQokDGbKwMBgMpw= github.com/kubescape/rbac-utils v0.0.17 h1:B78kjlTKqjYK/PXwmi4GPysHsFxIwVz1KFb4+IGT29w= github.com/kubescape/rbac-utils v0.0.17/go.mod h1:pBwjpcrVeuH/no+DiCZWvlhYtCDzd3U0o/hEZKi+eM8= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= diff --git a/httphandler/go.mod b/httphandler/go.mod index 92ab1559..d8411424 100644 --- a/httphandler/go.mod +++ b/httphandler/go.mod @@ -12,7 +12,7 @@ require ( github.com/gorilla/schema v1.2.0 github.com/kubescape/go-logger v0.0.6 github.com/kubescape/kubescape/v2 v2.0.0-00010101000000-000000000000 - github.com/kubescape/opa-utils v0.0.181 + github.com/kubescape/opa-utils v0.0.186 github.com/stretchr/testify v1.8.0 k8s.io/utils v0.0.0-20220706174534-f6158b442e7c ) diff --git a/httphandler/go.sum b/httphandler/go.sum index 4bb05236..99bb4f24 100644 --- a/httphandler/go.sum +++ b/httphandler/go.sum @@ -895,8 +895,8 @@ github.com/kubescape/go-logger v0.0.6 h1:ynhAmwrz0O7Jtqq1CdmCZUrKveji25hVP+B/FAb github.com/kubescape/go-logger v0.0.6/go.mod h1:DnVWEvC90LFY1nNMaNo6nBVOcqkLMK3S0qzXP1fzRvI= github.com/kubescape/k8s-interface v0.0.83 h1:yQ1kWNZmKfBim/+NmxpPI/j7L9ASDq2h3mCNdmYgzqY= github.com/kubescape/k8s-interface v0.0.83/go.mod h1:ihX96yqar+xogHl45mFE8zT9DLI06iy7XQPAP+j5KJE= -github.com/kubescape/opa-utils v0.0.181 h1:At3/4+yS7NqIKYXRj4iwdLmrGOR6B6SyYMALme9axAI= -github.com/kubescape/opa-utils v0.0.181/go.mod h1:jC5QrhS6WiFj/tXP2/YSDBnFlGsYUQokDGbKwMBgMpw= +github.com/kubescape/opa-utils v0.0.186 h1:3T0lD3x1/hweXY/HeNM2I4h8Ugh9SacXPo1AaOPBrAs= +github.com/kubescape/opa-utils v0.0.186/go.mod h1:jC5QrhS6WiFj/tXP2/YSDBnFlGsYUQokDGbKwMBgMpw= github.com/kubescape/rbac-utils v0.0.17 h1:B78kjlTKqjYK/PXwmi4GPysHsFxIwVz1KFb4+IGT29w= github.com/kubescape/rbac-utils v0.0.17/go.mod h1:pBwjpcrVeuH/no+DiCZWvlhYtCDzd3U0o/hEZKi+eM8= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=