diff --git a/README.md b/README.md index 2860d179..ddee4832 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser | `-e`/`--exclude-namespaces` | Scan all namespaces | Namespaces to exclude from scanning. Recommended to exclude `kube-system` and `kube-public` namespaces | | | `--include-namespaces` | Scan all namespaces | Scan specific namespaces | | | `-s`/`--silent` | Display progress messages | Silent progress messages | | -| `-t`/`--fail-threshold` | `0` (do not fail) | fail command (return exit code 1) if result is below threshold | `0` -> `100` | +| `-t`/`--fail-threshold` | `100` (do not fail) | fail command (return exit code 1) if result is above threshold | `0` -> `100` | | `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus` | | `-o`/`--output` | print to stdout | Save scan result in file | | | `--use-from` | | Load local framework object from specified path. If not used will download latest | | diff --git a/cautils/customerloader.go b/cautils/customerloader.go index d6000681..fab50611 100644 --- a/cautils/customerloader.go +++ b/cautils/customerloader.go @@ -180,7 +180,9 @@ func NewClusterConfig(k8s *k8sinterface.KubernetesApi, backendAPI getter.IBacken } } if c.configObj.ClusterName == "" { - c.configObj.ClusterName = adoptClusterName(k8sinterface.GetClusterName()) + c.configObj.ClusterName = AdoptClusterName(k8sinterface.GetClusterName()) + } else { // override the cluster name if it has unwanted characters + c.configObj.ClusterName = AdoptClusterName(c.configObj.ClusterName) } return c @@ -425,6 +427,6 @@ func DeleteConfigFile() error { return os.Remove(ConfigFileFullPath()) } -func adoptClusterName(clusterName string) string { +func AdoptClusterName(clusterName string) string { return strings.ReplaceAll(clusterName, "/", "-") } diff --git a/cautils/versioncheck.go b/cautils/versioncheck.go index ab5d4217..aaa72795 100644 --- a/cautils/versioncheck.go +++ b/cautils/versioncheck.go @@ -65,6 +65,9 @@ func NewVersionCheckRequest(buildNumber, frameworkName, frameworkVersion, scanni if buildNumber == "" { buildNumber = UnknownBuildNumber } + if scanningTarget == "" { + scanningTarget = "unknown" + } return &VersionCheckRequest{ Client: "kubescape", ClientVersion: buildNumber, @@ -82,7 +85,7 @@ func (v *VersionCheckHandlerMock) CheckLatestVersion(versionData *VersionCheckRe func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckRequest) error { defer func() { if err := recover(); err != nil { - fmt.Println("failed to get latest version") + WarningDisplay(os.Stderr, "failed to get latest version\n") } }() @@ -93,7 +96,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques if latestVersion.ClientUpdate != "" { if BuildNumber != "" && BuildNumber < latestVersion.ClientUpdate { - fmt.Println(warningMessage(latestVersion.Client, latestVersion.ClientUpdate)) + WarningDisplay(os.Stderr, warningMessage(latestVersion.Client, latestVersion.ClientUpdate), "\n") } } @@ -103,7 +106,7 @@ func (v *VersionCheckHandler) CheckLatestVersion(versionData *VersionCheckReques // } if latestVersion.Message != "" { - fmt.Println(latestVersion.Message) + InfoDisplay(os.Stderr, latestVersion.Message, "\n") } return nil diff --git a/clihandler/cmd/download.go b/clihandler/cmd/download.go index 74e3cad3..bc4bd3ca 100644 --- a/clihandler/cmd/download.go +++ b/clihandler/cmd/download.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "os" "strings" "github.com/armosec/kubescape/cautils" @@ -25,43 +26,10 @@ var downloadCmd = &cobra.Command{ return nil }, RunE: func(cmd *cobra.Command, args []string) error { - if strings.EqualFold(args[0], "framework") { - downloadInfo.FrameworkName = strings.ToLower(args[1]) - g := getter.NewDownloadReleasedPolicy() - if err := g.SetRegoObjects(); err != nil { - return err - } - - if downloadInfo.Path == "" { - downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json") - } - frameworks, err := g.GetFramework(downloadInfo.FrameworkName) - if err != nil { - return err - } - err = getter.SaveFrameworkInFile(frameworks, downloadInfo.Path) - if err != nil { - return err - } - } else if strings.EqualFold(args[0], "control") { - downloadInfo.ControlName = strings.ToLower(args[1]) - g := getter.NewDownloadReleasedPolicy() - if err := g.SetRegoObjects(); err != nil { - return err - } - if downloadInfo.Path == "" { - downloadInfo.Path = getter.GetDefaultPath(downloadInfo.ControlName + ".json") - } - controls, err := g.GetControl(downloadInfo.ControlName) - if err != nil { - return err - } - err = getter.SaveControlInFile(controls, downloadInfo.Path) - if err != nil { - return err - } + if err := download(args); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) } - return nil }, } @@ -71,3 +39,57 @@ func init() { downloadInfo = cautils.DownloadInfo{} downloadCmd.Flags().StringVarP(&downloadInfo.Path, "output", "o", "", "Output file. If specified, will store save to `~/.kubescape/.json`") } + +func download(args []string) error { + switch strings.ToLower(args[0]) { + case "framework": + return downloadFramework(args[1]) + case "control": + return downloadControl(args[1]) + // case "exceptions": + // case "artifacts": + default: + return fmt.Errorf("unknown command to download") + } +} + +func downloadFramework(frameworkName string) error { + downloadInfo.FrameworkName = strings.ToLower(frameworkName) + g := getter.NewDownloadReleasedPolicy() + if err := g.SetRegoObjects(); err != nil { + return err + } + + if downloadInfo.Path == "" { + downloadInfo.Path = getter.GetDefaultPath(downloadInfo.FrameworkName + ".json") + } + frameworks, err := g.GetFramework(downloadInfo.FrameworkName) + if err != nil { + return err + } + err = getter.SaveFrameworkInFile(frameworks, downloadInfo.Path) + if err != nil { + return err + } + return nil +} + +func downloadControl(controlName string) error { + downloadInfo.ControlName = strings.ToLower(controlName) + g := getter.NewDownloadReleasedPolicy() + if err := g.SetRegoObjects(); err != nil { + return err + } + if downloadInfo.Path == "" { + downloadInfo.Path = getter.GetDefaultPath(downloadInfo.ControlName + ".json") + } + controls, err := g.GetControl(downloadInfo.ControlName) + if err != nil { + return err + } + err = getter.SaveControlInFile(controls, downloadInfo.Path) + if err != nil { + return err + } + return nil +} diff --git a/clihandler/cmd/framework.go b/clihandler/cmd/framework.go index e3533794..ca2aaf04 100644 --- a/clihandler/cmd/framework.go +++ b/clihandler/cmd/framework.go @@ -90,7 +90,7 @@ var frameworkCmd = &cobra.Command{ cautils.SetSilentMode(scanInfo.Silent) err := clihandler.ScanCliSetup(&scanInfo) if err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) + fmt.Fprintf(os.Stderr, "error: %v\n\n", err) os.Exit(1) } return nil diff --git a/clihandler/cmd/scan.go b/clihandler/cmd/scan.go index e45abd3e..9370f4ab 100644 --- a/clihandler/cmd/scan.go +++ b/clihandler/cmd/scan.go @@ -39,7 +39,7 @@ func init() { scanCmd.PersistentFlags().StringVar(&scanInfo.ControlsInputs, "controls-config", "", "Path to an controls-config obj. If not set will download controls-config from ARMO management portal") scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from ARMO management portal") scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public") - scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent below which the command fails and returns exit code 1") + scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1") scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"/"prometheus"`) scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b") scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results") @@ -50,7 +50,7 @@ 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") - hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensor, "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") + hostF := scanCmd.PersistentFlags().VarPF(&scanInfo.HostSensor, "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") hostF.NoOptDefVal = "true" hostF.DefValue = "false, for no TTY in stdin" } diff --git a/clihandler/initcli.go b/clihandler/initcli.go index 0c6900da..eebd806f 100644 --- a/clihandler/initcli.go +++ b/clihandler/initcli.go @@ -121,9 +121,8 @@ func ScanCliSetup(scanInfo *cautils.ScanInfo) error { // print report url interfaces.report.DisplayReportURL() - adjustedFailThreshold := float32(scanInfo.FailThreshold) / 100 - if score >= adjustedFailThreshold { - return fmt.Errorf("Scan score is below threshold") + if score >= float32(scanInfo.FailThreshold) { + return fmt.Errorf("scan risk-score %.2f is above permitted threshold %d", score, scanInfo.FailThreshold) } return nil diff --git a/clihandler/initcliutils.go b/clihandler/initcliutils.go index b7ce4c54..d455a54b 100644 --- a/clihandler/initcliutils.go +++ b/clihandler/initcliutils.go @@ -38,7 +38,6 @@ func getRBACHandler(tenantConfig cautils.ITenantConfig, k8s *k8sinterface.Kubern func getReporter(tenantConfig cautils.ITenantConfig, submit bool) reporter.IReport { if submit { return reporter.NewReportEventReceiver(tenantConfig.GetConfigObj()) - } return reporter.NewReportMock() } diff --git a/opaprocessor/processorhandler.go b/opaprocessor/processorhandler.go index 7a320b92..330d1aa6 100644 --- a/opaprocessor/processorhandler.go +++ b/opaprocessor/processorhandler.go @@ -67,9 +67,9 @@ func (opaHandler *OPAProcessorHandler) ProcessRulesListenner() { opap.updateResults() // update score - // opap.updateScore() scoreutil := score.NewScore(opaSessionObj.AllResources) scoreutil.Calculate(opaSessionObj.PostureReport.FrameworkReports) + // report *opaHandler.reportResults <- opaSessionObj } diff --git a/resultshandling/printer/prettyprinter.go b/resultshandling/printer/prettyprinter.go index b3a2ca36..3a8b416a 100644 --- a/resultshandling/printer/prettyprinter.go +++ b/resultshandling/printer/prettyprinter.go @@ -35,10 +35,12 @@ func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) warningResources := []string{} allResources := []string{} frameworkNames := []string{} + frameworkScores := []float32{} var overallRiskScore float32 = 0 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()...)) @@ -56,7 +58,7 @@ func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) } printer.printResults() - printer.printSummaryTable(frameworkNames) + printer.printSummaryTable(frameworkNames, frameworkScores) } @@ -218,9 +220,9 @@ func generateFooter(printer *PrettyPrinter) []string { return row } -func (printer *PrettyPrinter) printSummaryTable(frameworksNames []string) { +func (printer *PrettyPrinter) printSummaryTable(frameworksNames []string, frameworkScores []float32) { // For control scan framework will be nil - printer.printFramework(frameworksNames) + printer.printFramework(frameworksNames, frameworkScores) summaryTable := tablewriter.NewWriter(printer.writer) summaryTable.SetAutoWrapText(false) @@ -240,16 +242,16 @@ func (printer *PrettyPrinter) printSummaryTable(frameworksNames []string) { summaryTable.Render() } -func (printer *PrettyPrinter) printFramework(frameworksNames []string) { +func (printer *PrettyPrinter) printFramework(frameworksNames []string, frameworkScores []float32) { if len(frameworksNames) == 1 { - cautils.InfoTextDisplay(printer.writer, fmt.Sprintf("%s FRAMEWORK\n", frameworksNames[0])) + cautils.InfoTextDisplay(printer.writer, fmt.Sprintf("FRAMEWORK %s\n", frameworksNames[0])) } else if len(frameworksNames) > 1 { - p := "" + p := "FRAMEWORKS: " for i := 0; i < len(frameworksNames)-1; i++ { - p += frameworksNames[i] + ", " + p += fmt.Sprintf("%s (risk: %.2f), ", frameworksNames[i], frameworkScores[i]) } - p += frameworksNames[len(frameworksNames)-1] - cautils.InfoTextDisplay(printer.writer, fmt.Sprintf("%s FRAMEWORKS\n", p)) + p += fmt.Sprintf("%s (risk: %.2f)\n", frameworksNames[len(frameworksNames)-1], frameworkScores[len(frameworkScores)-1]) + cautils.InfoTextDisplay(printer.writer, p) } } diff --git a/resultshandling/reporter/reporteventreceiver.go b/resultshandling/reporter/reporteventreceiver.go index fc01232d..95b70f9c 100644 --- a/resultshandling/reporter/reporteventreceiver.go +++ b/resultshandling/reporter/reporteventreceiver.go @@ -43,6 +43,10 @@ func NewReportEventReceiver(tenantConfig *cautils.ConfigObj) *ReportEventReceive func (report *ReportEventReceiver) ActionSendReport(opaSessionObj *cautils.OPASessionObj) error { + if report.customerGUID == "" || report.clusterName == "" { + return fmt.Errorf("missing accout ID or cluster name. AccountID: '%s', Cluster name: '%s'", report.customerGUID, report.clusterName) + } + if err := report.prepareReport(opaSessionObj.PostureReport, opaSessionObj.AllResources); err != nil { return err } @@ -54,7 +58,7 @@ func (report *ReportEventReceiver) SetCustomerGUID(customerGUID string) { } func (report *ReportEventReceiver) SetClusterName(clusterName string) { - report.clusterName = clusterName + report.clusterName = cautils.AdoptClusterName(clusterName) // clean cluster name } func (report *ReportEventReceiver) prepareReport(postureReport *reporthandling.PostureReport, allResources map[string]workloadinterface.IMetadata) error {