diff --git a/README.md b/README.md index 0d194e32..07c4d229 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ kubescape scan --verbose #### Output in `json` format ``` -kubescape scan --format json --output results.json +kubescape scan --format json --format-version v2 --output results.json ``` #### Output in `junit xml` format diff --git a/cautils/scaninfo.go b/cautils/scaninfo.go index 7f21ff3d..f8b7e7f7 100644 --- a/cautils/scaninfo.go +++ b/cautils/scaninfo.go @@ -68,7 +68,7 @@ type ScanInfo struct { VerboseMode bool // Display all of the input resources and not only failed resources Format string // Format results (table, json, junit ...) Output string // Store results in an output file, Output file name - OutputVersion string // Output object can be differnet between versions, this is for testing and backward compatibility + FormatVersion string // Output object can be differnet between versions, this is for testing and backward compatibility ExcludedNamespaces string // used for host sensor namespace IncludeNamespaces string // DEPRECATED? InputPatterns []string // Yaml files input patterns diff --git a/clihandler/cmd/scan.go b/clihandler/cmd/scan.go index 1835321d..d2aabd74 100644 --- a/clihandler/cmd/scan.go +++ b/clihandler/cmd/scan.go @@ -79,12 +79,12 @@ func init() { scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages") scanCmd.PersistentFlags().BoolVarP(&scanInfo.Submit, "submit", "", false, "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 submitted") scanCmd.PersistentFlags().StringVar(&scanInfo.HostSensorYamlPath, "host-scan-yaml", "", "Override default host sensor DaemonSet. Use this flag cautiously") - scanCmd.PersistentFlags().StringVar(&scanInfo.OutputVersion, "output-version", "v1", "Output object can be differnet between versions, this is for testing and backward compatibility") + scanCmd.PersistentFlags().StringVar(&scanInfo.FormatVersion, "format-version", "v1", "Output object can be differnet between versions, this is for testing and backward compatibility") // hidden flags scanCmd.PersistentFlags().MarkHidden("host-scan-yaml") // this flag should be used very cautiously. We prefer users will not use it at all unless the DaemoSet can not run pods on the nodes scanCmd.PersistentFlags().MarkHidden("silent") // this flag should be deprecated since we added the --logger support - scanCmd.PersistentFlags().MarkHidden("output-version") // meant for testing different output approaches and not for common use + scanCmd.PersistentFlags().MarkHidden("format-version") // meant for testing different output approaches and not for common use hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensorEnabled, "enable-host-scan", "", "Deploy ARMO K8s host-sensor daemonset in the scanned cluster. Deleting it right after we collecting the data. Required to collect valueable data from cluster nodes for certain controls. Yaml file: https://raw.githubusercontent.com/armosec/kubescape/master/hostsensorutils/hostsensor.yaml") hostF.NoOptDefVal = "true" diff --git a/clihandler/initcli.go b/clihandler/initcli.go index 0a96a51f..def16384 100644 --- a/clihandler/initcli.go +++ b/clihandler/initcli.go @@ -83,7 +83,7 @@ func getInterfaces(scanInfo *cautils.ScanInfo) componentInterfaces { reportHandler := getReporter(tenantConfig, scanInfo.Submit) // setup printer - printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.OutputVersion, scanInfo.VerboseMode) + printerHandler := resultshandling.NewPrinter(scanInfo.Format, scanInfo.FormatVersion, scanInfo.VerboseMode) printerHandler.SetWriter(scanInfo.Output) // ================== return interface ====================================== diff --git a/resultshandling/printer/v2/jsonprinter.go b/resultshandling/printer/v2/jsonprinter.go new file mode 100644 index 00000000..d9b1235c --- /dev/null +++ b/resultshandling/printer/v2/jsonprinter.go @@ -0,0 +1,36 @@ +package v2 + +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) { + finalizeJson(opaSessionObj) + r, err := json.Marshal(opaSessionObj.Report) + if err != nil { + logger.L().Fatal("failed to Marshal posture report object") + } + jsonPrinter.writer.Write(r) +} diff --git a/resultshandling/printer/v2/junit.go b/resultshandling/printer/v2/junit.go index 4069c597..503b7eaf 100644 --- a/resultshandling/printer/v2/junit.go +++ b/resultshandling/printer/v2/junit.go @@ -103,10 +103,6 @@ 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) FinalizeData(opaSessionObj *cautils.OPASessionObj) { - finalizeReport(opaSessionObj) -} - func (junitPrinter *JunitPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { junitResult := testsSuites(opaSessionObj) postureReportStr, err := xml.Marshal(junitResult) diff --git a/resultshandling/printer/v2/prettyprinter.go b/resultshandling/printer/v2/prettyprinter.go index 75e7053b..314149f5 100644 --- a/resultshandling/printer/v2/prettyprinter.go +++ b/resultshandling/printer/v2/prettyprinter.go @@ -17,24 +17,27 @@ import ( ) type PrettyPrinter struct { - outputVersion string + formatVersion string writer *os.File verboseMode bool sortedControlNames []string } -func NewPrettyPrinter(verboseMode bool, outputVersion string) *PrettyPrinter { +func NewPrettyPrinter(verboseMode bool, formatVersion string) *PrettyPrinter { return &PrettyPrinter{ verboseMode: verboseMode, - outputVersion: outputVersion, + formatVersion: formatVersion, } } func (prettyPrinter *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { prettyPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) // ListControls().All()) - prettyPrinter.resourceTable(opaSessionObj.ResourcesResult, opaSessionObj.AllResources) - prettyPrinter.printResults(&opaSessionObj.Report.SummaryDetails.Controls, opaSessionObj.AllResources) + if prettyPrinter.formatVersion == "v1" { + prettyPrinter.resourceTable(opaSessionObj.ResourcesResult, opaSessionObj.AllResources) + } else if prettyPrinter.formatVersion == "v2" { + prettyPrinter.printResults(&opaSessionObj.Report.SummaryDetails.Controls, opaSessionObj.AllResources) + } prettyPrinter.printSummaryTable(&opaSessionObj.Report.SummaryDetails) } @@ -47,10 +50,6 @@ func (prettyPrinter *PrettyPrinter) Score(score float32) { } func (prettyPrinter *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries, allResources map[string]workloadinterface.IMetadata) { - if prettyPrinter.outputVersion != "v1" { - return - } - for i := 0; i < len(prettyPrinter.sortedControlNames); i++ { controlSummary := controls.GetControl(reportsummary.EControlCriteriaName, prettyPrinter.sortedControlNames[i]) // summaryDetails.Controls ListControls().All() Controls.GetControl(ca) diff --git a/resultshandling/printer/v2/resourcetable.go b/resultshandling/printer/v2/resourcetable.go index 1689ed3d..b1555fc1 100644 --- a/resultshandling/printer/v2/resourcetable.go +++ b/resultshandling/printer/v2/resourcetable.go @@ -11,9 +11,7 @@ import ( ) func (prettyPrinter *PrettyPrinter) resourceTable(results map[string]resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) { - if prettyPrinter.outputVersion != "v2" { - return - } + summaryTable := tablewriter.NewWriter(prettyPrinter.writer) summaryTable.SetAutoWrapText(true) summaryTable.SetAutoMergeCells(true) diff --git a/resultshandling/printer/v2/utils.go b/resultshandling/printer/v2/utils.go index c432bcac..31c4372b 100644 --- a/resultshandling/printer/v2/utils.go +++ b/resultshandling/printer/v2/utils.go @@ -3,22 +3,22 @@ package v2 import ( "github.com/armosec/k8s-interface/workloadinterface" "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/cautils/logger" + "github.com/armosec/kubescape/cautils/logger/helpers" "github.com/armosec/opa-utils/reporthandling" "github.com/armosec/opa-utils/reporthandling/results/v1/resourcesresults" ) // finalizeV2Report finalize the results objects by copying data from map to lists -func finalizeReport(opaSessionObj *cautils.OPASessionObj) { +func finalizeJson(opaSessionObj *cautils.OPASessionObj) { if len(opaSessionObj.Report.Results) == 0 { opaSessionObj.Report.Results = make([]resourcesresults.Result, len(opaSessionObj.ResourcesResult)) finalizeResults(opaSessionObj.Report.Results, opaSessionObj.ResourcesResult) - opaSessionObj.ResourcesResult = nil } if len(opaSessionObj.Report.Resources) == 0 { opaSessionObj.Report.Resources = make([]reporthandling.Resource, len(opaSessionObj.AllResources)) - finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.AllResources) - opaSessionObj.AllResources = nil + finalizeResources(opaSessionObj.Report.Resources, opaSessionObj.Report.Results, opaSessionObj.AllResources) } } @@ -30,15 +30,22 @@ func finalizeResults(results []resourcesresults.Result, resourcesResult map[stri } } -func finalizeResources(resources []reporthandling.Resource, allResources map[string]workloadinterface.IMetadata) { +func finalizeResources(resources []reporthandling.Resource, results []resourcesresults.Result, allResources map[string]workloadinterface.IMetadata) { index := 0 - for resourceID := range allResources { - if obj, ok := allResources[resourceID]; ok { + for i := range results { + if obj, ok := allResources[results[i].ResourceID]; ok { r := *reporthandling.NewResource(obj.GetObject()) - r.ResourceID = resourceID + r.ResourceID = results[i].ResourceID resources[index] = r } index++ } } + +func logOUtputFile(fileName string) { + if fileName != "/dev/stdout" && fileName != "/dev/stderr" { + logger.L().Success("Scan results saved", helpers.String("filename", fileName)) + } + +} diff --git a/resultshandling/results.go b/resultshandling/results.go index e5ec8ec5..b74f04d9 100644 --- a/resultshandling/results.go +++ b/resultshandling/results.go @@ -53,11 +53,16 @@ func CalculatePostureScore(postureReport *reporthandling.PostureReport) float32 return (float32(len(allResources)) - float32(len(failedResources))) / float32(len(allResources)) } -func NewPrinter(printFormat, outputVersion string, verboseMode bool) printer.IPrinter { +func NewPrinter(printFormat, formatVersion string, verboseMode bool) printer.IPrinter { switch printFormat { case printer.JsonFormat: - return printerv1.NewJsonPrinter() + switch formatVersion { + case "v2": + return printerv2.NewJsonPrinter() + default: + return printerv1.NewJsonPrinter() + } case printer.JunitResultFormat: return printerv2.NewJunitPrinter(verboseMode) case printer.PrometheusFormat: @@ -65,6 +70,6 @@ func NewPrinter(printFormat, outputVersion string, verboseMode bool) printer.IPr case printer.PdfFormat: return printerv2.NewPdfPrinter() default: - return printerv2.NewPrettyPrinter(verboseMode, outputVersion) + return printerv2.NewPrettyPrinter(verboseMode, formatVersion) } }