diff --git a/README.md b/README.md index 4608f65b..ef679e9d 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Want to contribute? Want to discuss something? Have an issue? * [Scan Kubescape on an air-gapped environment (offline support)](https://youtu.be/IGXL9s37smM) * [Managing exceptions in the Kubescape SaaS version](https://youtu.be/OzpvxGmCR80) * [Configure and run customized frameworks](https://youtu.be/12Sanq_rEhs) +* Customize controls configurations. [Kubescape CLI](https://youtu.be/955psg6TVu4), [Kubescape SaaS](https://youtu.be/lIMVSVhH33o) ## Install on Windows @@ -109,7 +110,7 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser | `--use-artifacts-from` | | Load artifacts (frameworks, control-config, exceptions) from local directory. If not used will download them | | | `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` | | `--exceptions` | | Path to an exceptions obj, [examples](https://github.com/armosec/kubescape/tree/master/examples/exceptions/README.md). Default will download exceptions from Kubescape SaaS || -| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal | | +| `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal. [docs](https://hub.armo.cloud/docs/configuration-parameters) | | | `--submit` | `false` | If set, Kubescape will send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not sent | `true`/`false` | | `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false` | | `--account` | | Armo portal account ID. Default will load account ID from configMap or config file | | diff --git a/cautils/getter/armoapi.go b/cautils/getter/armoapi.go index ddc9708a..e789b165 100644 --- a/cautils/getter/armoapi.go +++ b/cautils/getter/armoapi.go @@ -126,6 +126,13 @@ func (armoAPI *ArmoAPI) Post(fullURL string, headers map[string]string, body []b return HttpPost(armoAPI.httpClient, fullURL, headers, body) } +func (armoAPI *ArmoAPI) Delete(fullURL string, headers map[string]string) (string, error) { + if headers == nil { + headers = make(map[string]string) + } + armoAPI.appendAuthHeaders(headers) + return HttpDelete(armoAPI.httpClient, fullURL, headers) +} func (armoAPI *ArmoAPI) Get(fullURL string, headers map[string]string) (string, error) { if headers == nil { headers = make(map[string]string) @@ -293,7 +300,7 @@ func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPo if err != nil { return err } - _, err = armoAPI.Post(armoAPI.postExceptionsURL(), map[string]string{"Content-Type": "application/json"}, ex) + _, err = armoAPI.Post(armoAPI.exceptionsURL(""), map[string]string{"Content-Type": "application/json"}, ex) if err != nil { return err } @@ -301,6 +308,14 @@ func (armoAPI *ArmoAPI) PostExceptions(exceptions []armotypes.PostureExceptionPo return nil } +func (armoAPI *ArmoAPI) DeleteException(exceptionName string) error { + + _, err := armoAPI.Delete(armoAPI.exceptionsURL(exceptionName), nil) + if err != nil { + return err + } + return nil +} func (armoAPI *ArmoAPI) Login() error { if armoAPI.accountID == "" { return fmt.Errorf("failed to login, missing accountID") diff --git a/cautils/getter/armoapiutils.go b/cautils/getter/armoapiutils.go index 0b213b48..ce55e6f5 100644 --- a/cautils/getter/armoapiutils.go +++ b/cautils/getter/armoapiutils.go @@ -56,7 +56,7 @@ func (armoAPI *ArmoAPI) getExceptionsURL(clusterName string) string { return u.String() } -func (armoAPI *ArmoAPI) postExceptionsURL() string { +func (armoAPI *ArmoAPI) exceptionsURL(exceptionsPolicyName string) string { u := url.URL{} u.Scheme = "https" u.Host = armoAPI.apiURL @@ -64,6 +64,10 @@ func (armoAPI *ArmoAPI) postExceptionsURL() string { q := u.Query() q.Add("customerGUID", armoAPI.getCustomerGUIDFallBack()) + if exceptionsPolicyName != "" { // for delete + q.Add("policyName", exceptionsPolicyName) + } + u.RawQuery = q.Encode() return u.String() diff --git a/cautils/getter/getpoliciesutils.go b/cautils/getter/getpoliciesutils.go index d577234a..7d822eb3 100644 --- a/cautils/getter/getpoliciesutils.go +++ b/cautils/getter/getpoliciesutils.go @@ -47,6 +47,24 @@ func JSONDecoder(origin string) *json.Decoder { return dec } +func HttpDelete(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) { + + req, err := http.NewRequest("DELETE", fullURL, nil) + if err != nil { + return "", err + } + setHeaders(req, headers) + + resp, err := httpClient.Do(req) + if err != nil { + return "", err + } + respStr, err := httpRespToString(resp) + if err != nil { + return "", err + } + return respStr, nil +} func HttpGetter(httpClient *http.Client, fullURL string, headers map[string]string) (string, error) { req, err := http.NewRequest("GET", fullURL, nil) diff --git a/clihandler/cliconfigdelete.go b/clihandler/cliconfigdelete.go new file mode 100644 index 00000000..77e9f7ba --- /dev/null +++ b/clihandler/cliconfigdelete.go @@ -0,0 +1,7 @@ +package clihandler + +func CliDelete() error { + + tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface + return tenant.DeleteCachedConfig() +} diff --git a/clihandler/clidelete.go b/clihandler/clidelete.go index 77e9f7ba..531b34d4 100644 --- a/clihandler/clidelete.go +++ b/clihandler/clidelete.go @@ -1,7 +1,35 @@ package clihandler -func CliDelete() error { +import ( + "fmt" - tenant := getTenantConfig("", "", getKubernetesApi()) // change k8sinterface - return tenant.DeleteCachedConfig() + "github.com/armosec/kubescape/cautils/getter" + "github.com/armosec/kubescape/cautils/logger" + "github.com/armosec/kubescape/cautils/logger/helpers" +) + +func DeleteExceptions(accountID string, exceptions []string) error { + + // load cached config + getTenantConfig(accountID, "", getKubernetesApi()) + + // login kubescape SaaS + armoAPI := getter.GetArmoAPIConnector() + if err := armoAPI.Login(); err != nil { + return err + } + + for i := range exceptions { + exceptionName := exceptions[i] + if exceptionName == "" { + continue + } + logger.L().Info("Deleting exception", helpers.String("name", exceptionName)) + if err := armoAPI.DeleteException(exceptionName); err != nil { + return fmt.Errorf("failed to delete exception '%s', reason: %s", exceptionName, err.Error()) + } + logger.L().Success("Exception deleted successfully") + } + + return nil } diff --git a/clihandler/clilist.go b/clihandler/clilist.go index aa6920d3..b2c81cee 100644 --- a/clihandler/clilist.go +++ b/clihandler/clilist.go @@ -13,6 +13,7 @@ import ( var listFunc = map[string]func(*cliobjects.ListPolicies) ([]string, error){ "controls": listControls, "frameworks": listFrameworks, + "exceptions": listExceptions, } var listFormatFunc = map[string]func(*cliobjects.ListPolicies, []string){ @@ -60,13 +61,25 @@ func listControls(listPolicies *cliobjects.ListPolicies) ([]string, error) { return g.ListControls(l) } +func listExceptions(listPolicies *cliobjects.ListPolicies) ([]string, error) { + // load tenant config + getTenantConfig(listPolicies.Account, "", getKubernetesApi()) + + var exceptionsNames []string + armoAPI := getExceptionsGetter("") + exceptions, err := armoAPI.GetExceptions("") + if err != nil { + return exceptionsNames, err + } + for i := range exceptions { + exceptionsNames = append(exceptionsNames, exceptions[i].Name) + } + return exceptionsNames, nil +} + func prettyPrintListFormat(listPolicies *cliobjects.ListPolicies, policies []string) { sep := "\n * " - usageCmd := strings.TrimSuffix(listPolicies.Target, "s") fmt.Printf("Supported %s:%s%s\n", listPolicies.Target, sep, strings.Join(policies, sep)) - fmt.Printf("\nUsage:\n") - fmt.Printf("$ kubescape scan %s \"name\"\n", usageCmd) - fmt.Printf("$ kubescape scan %s \"name-0\",\"name-1\"\n\n", usageCmd) } func jsonListFormat(listPolicies *cliobjects.ListPolicies, policies []string) { diff --git a/clihandler/cliobjects/submit.go b/clihandler/cliobjects/submit.go index e250e880..9e8ad02e 100644 --- a/clihandler/cliobjects/submit.go +++ b/clihandler/cliobjects/submit.go @@ -3,3 +3,7 @@ package cliobjects type Submit struct { Account string } + +type Delete struct { + Account string +} diff --git a/clihandler/cmd/delete.go b/clihandler/cmd/delete.go new file mode 100644 index 00000000..92bdedb4 --- /dev/null +++ b/clihandler/cmd/delete.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/armosec/kubescape/cautils/logger" + "github.com/armosec/kubescape/clihandler" + "github.com/armosec/kubescape/clihandler/cliobjects" + "github.com/spf13/cobra" +) + +var deleteInfo cliobjects.Delete + +var deleteExceptionsExamples = ` + # Delete single exception + kubescape delete exceptions "exception name" + + # Delete multiple exceptions + kubescape delete exceptions "first exception;second exception;third exception" +` + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete configurations in Kubescape SaaS version", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + }, +} + +var deleteExceptionsCmd = &cobra.Command{ + Use: "exceptions ", + Short: "Delete exceptions from Kubescape SaaS version. Run 'kubescape list exceptions' for all exceptions names", + Example: deleteExceptionsExamples, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("missing exceptions names") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + exceptionsNames := strings.Split(args[0], ";") + if len(exceptionsNames) == 0 { + logger.L().Fatal("missing exceptions names") + } + if err := clihandler.DeleteExceptions(deleteInfo.Account, exceptionsNames); err != nil { + logger.L().Fatal(err.Error()) + } + }, +} + +func init() { + deleteCmd.PersistentFlags().StringVarP(&deleteInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file") + rootCmd.AddCommand(deleteCmd) + + deleteCmd.AddCommand(deleteExceptionsCmd) +} diff --git a/clihandler/cmd/root.go b/clihandler/cmd/root.go index 8c3dff7c..7bc135b7 100644 --- a/clihandler/cmd/root.go +++ b/clihandler/cmd/root.go @@ -13,6 +13,7 @@ import ( ) var armoBEURLs = "" +var armoBEURLsDep = "" var rootInfo cautils.RootInfo const envFlagUsage = "Send report results to specific URL. Format:,,.\n\t\tExample:report.armo.cloud,api.armo.cloud,portal.armo.cloud" @@ -47,8 +48,12 @@ func init() { cobra.OnInitialize(initLogger, initLoggerLevel, initEnvironment, initCacheDir) - rootCmd.PersistentFlags().StringVar(&armoBEURLs, "environment", "", envFlagUsage) + rootCmd.PersistentFlags().StringVar(&armoBEURLsDep, "environment", "", envFlagUsage) + rootCmd.PersistentFlags().StringVar(&armoBEURLs, "env", "", envFlagUsage) + rootCmd.PersistentFlags().MarkDeprecated("environment", "use 'env' instead") rootCmd.PersistentFlags().MarkHidden("environment") + rootCmd.PersistentFlags().MarkHidden("env") + rootCmd.PersistentFlags().StringVarP(&rootInfo.Logger, "logger", "l", helpers.InfoLevel.String(), fmt.Sprintf("Logger level. Supported: %s [$KS_LOGGER]", strings.Join(helpers.SupportedLevels(), "/"))) rootCmd.PersistentFlags().StringVar(&rootInfo.CacheDir, "cache-dir", getter.DefaultLocalStore, "Cache directory [$KS_CACHE_DIR]") } @@ -80,6 +85,9 @@ func initCacheDir() { logger.L().Debug("cache dir updated", helpers.String("path", getter.DefaultLocalStore)) } func initEnvironment() { + if armoBEURLsDep != "" { + armoBEURLs = armoBEURLsDep + } urlSlices := strings.Split(armoBEURLs, ",") if len(urlSlices) != 1 && len(urlSlices) < 3 { logger.L().Fatal("expected at least 3 URLs (report, api, frontend, auth)") diff --git a/examples/exceptions/exclude-kube-namespaces.json b/examples/exceptions/exclude-kube-namespaces.json index a8aecd4a..d2aee52b 100644 --- a/examples/exceptions/exclude-kube-namespaces.json +++ b/examples/exceptions/exclude-kube-namespaces.json @@ -24,17 +24,6 @@ "namespace": "kube-node-lease" } } - ], - "posturePolicies": [ - { - "frameworkName": "NSA" - }, - { - "frameworkName": "MITRE" - }, - { - "frameworkName": "ArmoBest" - } ] } ] \ No newline at end of file diff --git a/resultshandling/printer/v2/junit.go b/resultshandling/printer/v2/junit.go index 987a4f79..7b06e503 100644 --- a/resultshandling/printer/v2/junit.go +++ b/resultshandling/printer/v2/junit.go @@ -4,56 +4,85 @@ import ( "encoding/xml" "fmt" "os" + "strings" - "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" - "github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary" ) +/* +riskScore +status + +*/ type JunitPrinter struct { writer *os.File verbose bool } +// https://llg.cubic.org/docs/junit/ + +type JUnitXML struct { + TestSuites JUnitTestSuites `xml:"testsuites"` +} + +// JUnitTestSuites represents the test summary 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 + XMLName xml.Name `xml:"testsuites"` + Suites []JUnitTestSuite `xml:"testsuite"` // list of controls + Errors int `xml:"errors,attr"` // total number of tests with error result from all testsuites + Disabled int `xml:"disabled,attr"` // total number of disabled tests from all testsuites + Failures int `xml:"failures,attr"` // total number of failed tests from all testsuites + Tests int `xml:"tests,attr"` // total number of tests from all testsuites. Some software may expect to only see the number of successful tests from all testsuites though + Time string `xml:"time,attr"` // time in seconds to execute all test suites + Name string `xml:"name,attr"` // ? Add framework names ? } -type JUnitFrameworks struct { // Frameworks - Name string `xml:"name,attr"` - RiskScore float32 `xml:"riskscore,attr"` - Status string `xml:"status,attr"` +// JUnitTestSuite represents a single control +type JUnitTestSuite struct { + XMLName xml.Name `xml:"testsuite"` + Name string `xml:"name,attr"` // Full (class) name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents. Required + Disabled int `xml:"disabled,attr"` // The total number of disabled tests in the suite. optional. not supported by maven surefire. + Errors int `xml:"errors,attr"` // The total number of tests in the suite that errored + Failures int `xml:"failures,attr"` // The total number of tests in the suite that failed + Hostname string `xml:"hostname,attr"` // Host on which the tests were executed ? cluster name ? + ID int `xml:"id,attr"` // Starts at 0 for the first testsuite and is incremented by 1 for each following testsuite + 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) + Properties []JUnitProperty `xml:"properties>property,omitempty"` + TestCases []JUnitTestCase `xml:"testcase"` } -// 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"` +// JUnitTestCase represents a single resource +type JUnitTestCase struct { + XMLName xml.Name `xml:"testcase"` + Classname string `xml:"classname,attr"` // Full class name for the class the test method is in. required + Status string `xml:"status,attr"` // Status + Name string `xml:"name,attr"` // Name of the test method, required + Time string `xml:"time,attr"` // Time taken (in seconds) to execute the test. optional + SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` + Failure *JUnitFailure `xml:"failure,omitempty"` } -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"` +// 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 NewJunitPrinter(verbose bool) *JunitPrinter { @@ -87,70 +116,166 @@ func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionO } func (junitPrinter *JunitPrinter) convertPostureReportToJunitResult(results *cautils.OPASessionObj) (*JUnitTestSuites, error) { - juResult := JUnitTestSuites{ + + // // 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) + // } + testSuites := JUnitTestSuites{ XMLName: xml.Name{ Local: "Kubescape scan results", }, - RiskScore: results.Report.SummaryDetails.Score, - Time: results.Report.GetTimestamp().String(), - Controls: len(results.Report.ListControls().All()), - Frameworks: []JUnitFrameworks{}, } + testSuites.Failures = results.Report.SummaryDetails.NumberOfResources().Failed() + testSuites.Tests = results.Report.SummaryDetails.NumberOfResources().All() + testSuites.Disabled = results.Report.SummaryDetails.NumberOfResources().Skipped() + // summary.errors = + // summary.Name = "?" - // 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) - } + // resources + counter := 0 + for resourceID, resourceResult := range results.ResourcesResult { + counter++ - // controls - for _, controlIDs := range results.Report.ListControlsIDs().All() { - controlReport := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, controlIDs) - - // control data - testCase := JUnitTestCase{} - 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 - var jUnitResources []JUnitResource - 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() - } - - jUnitResources = append(jUnitResources, jUnitResource) + // resource data + testSuite := JUnitTestSuite{ + XMLName: xml.Name{ + Local: resourceID, + }, } - testCase.Resources = jUnitResources - juResult.Suites = append(juResult.Suites, testCase) + testSuite.Name = resourceID + testSuite.Disabled = 0 + testSuite.Errors = 0 + testSuite.Failures = len(resourceResult.ListControlsIDs(nil).Failed()) + testSuite.Hostname = "" + testSuite.ID = counter + // testSuite.Skipped = "" + testSuite.Time = "" + testSuite.Timestamp = results.PostureReport.ReportGenerationTime.String() + + testSuite.Properties = []JUnitProperty{ + { + Name: "ID", + Value: resourceID, + }, + } + + // controls + for _, control := range resourceResult.ListControls() { + testCase := JUnitTestCase{ + XMLName: xml.Name{ + Local: control.GetName(), + Space: getControlURL(control.GetID()), + }, + } + testCase.Name = control.GetName() + testCase.Classname = control.GetID() + testCase.Status = string(control.GetStatus(nil).Status()) + + if control.GetStatus(nil).IsFailed() { + paths := failedPathsToString(&control) + + testCaseFailure := JUnitFailure{} + testCaseFailure.Contents = fmt.Sprintf("More deatiles: %s", getControlURL(control.GetID())) + testCaseFailure.Message = strings.Join(paths, ";") + testCaseFailure.Type = "" // TODO - suppot add/modify + + testCase.Failure = &testCaseFailure + } + + testSuite.TestCases = append(testSuite.TestCases, testCase) + } + + testSuites.Suites = append(testSuites.Suites, testSuite) } - return &juResult, nil + return &testSuites, nil } + +// func (junitPrinter *JunitPrinter) convertPostureReportToJunitResult(results *cautils.OPASessionObj) (*JUnitTestSuites, error) { + +// // // 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) +// // } +// testSuites := JUnitTestSuites{ +// XMLName: xml.Name{ +// Local: "Kubescape scan results", +// }, +// } +// testSuites.Failures = results.Report.SummaryDetails.NumberOfControls().Failed() +// testSuites.Tests = results.Report.SummaryDetails.NumberOfControls().All() +// testSuites.Disabled = results.Report.SummaryDetails.NumberOfControls().Skipped() +// // summary.errors = +// // summary.Name = "?" + +// // controls +// for _, controlIDs := range results.Report.ListControlsIDs().All() { +// controlReport := results.Report.SummaryDetails.Controls.GetControl(reportsummary.EControlCriteriaID, controlIDs) + +// // control data +// testSuite := JUnitTestSuite{ +// XMLName: xml.Name{ +// Local: controlReport.GetName(), +// Space: getControlURL(controlReport.GetID()), +// }, +// } +// testSuite.Name = controlReport.GetName() +// testSuite.Disabled = 0 +// testSuite.Errors = 0 +// testSuite.Failures = controlReport.NumberOfResources().Failed() +// testSuite.Hostname = "" +// testSuite.ID = 0 +// // testSuite.Skipped = "" +// testSuite.Time = "" +// testSuite.Timestamp = "" + +// testCase.Classname = 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 +// var jUnitResources []JUnitResource +// 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() +// } + +// jUnitResources = append(jUnitResources, jUnitResource) +// } +// testCase.Resources = jUnitResources +// juResult.Suites = append(juResult.Suites, testCase) +// } + +// return &juResult, nil +// } diff --git a/resultshandling/printer/v2/resourcetable.go b/resultshandling/printer/v2/resourcetable.go index 843963bf..1689ed3d 100644 --- a/resultshandling/printer/v2/resourcetable.go +++ b/resultshandling/printer/v2/resourcetable.go @@ -55,18 +55,8 @@ func generateResourceRows(resource workloadinterface.IMetadata, controls []resou row = append(row, fmt.Sprintf("%s\nhttps://hub.armo.cloud/docs/%s", controls[i].GetName(), strings.ToLower(controls[i].GetID()))) row = append(row, resource.GetNamespace()) - var paths []string - for j := range controls[i].ResourceAssociatedRules { - for k := range controls[i].ResourceAssociatedRules[j].Paths { - if p := controls[i].ResourceAssociatedRules[j].Paths[k].FailedPath; p != "" { - paths = append(paths, p) - } - if p := controls[i].ResourceAssociatedRules[j].Paths[k].FixPath.Path; p != "" { - v := controls[i].ResourceAssociatedRules[j].Paths[k].FixPath.Value - paths = append(paths, fmt.Sprintf("%s=%s", p, v)) - } - } - } + paths := failedPathsToString(&controls[i]) + row = append(row, fmt.Sprintf("%s/%s\n%s", resource.GetKind(), resource.GetName(), strings.Join(paths, ";\n"))) row = append(row, string(controls[i].GetStatus(nil).Status())) rows = append(rows, row) @@ -94,3 +84,20 @@ func (a Matrix) Less(i, j int) bool { } return true } + +func failedPathsToString(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].FailedPath; p != "" { + paths = append(paths, p) + } + 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)) + } + } + } + return paths +}