diff --git a/cautils/customerloader.go b/cautils/customerloader.go index 44b79930..dce4a9db 100644 --- a/cautils/customerloader.go +++ b/cautils/customerloader.go @@ -129,7 +129,7 @@ func (c *EmptyConfig) GetCustomerGUID() string { return "" } func (c *EmptyConfig) GetK8sAPI() *k8sinterface.KubernetesApi { return nil } // TODO: return mock obj func (c *EmptyConfig) GetDefaultNS() string { return k8sinterface.GetDefaultNamespace() } func (c *EmptyConfig) GetBackendAPI() getter.IBackend { return nil } // TODO: return mock obj -func (c *EmptyConfig) GetClusterName() string { return k8sinterface.GetClusterName() } +func (c *EmptyConfig) GetClusterName() string { return adoptClusterName(k8sinterface.GetClusterName()) } func (c *EmptyConfig) GenerateURL() { message := fmt.Sprintf("\nCheckout for more cool features: https://%s\n", getter.GetArmoAPIConnector().GetFrontendURL()) InfoTextDisplay(os.Stdout, fmt.Sprintf("\n%s\n", message)) @@ -243,7 +243,7 @@ func (c *ClusterConfig) setCustomerGUID(customerGUID string) { } func (c *ClusterConfig) setClusterName(clusterName string) { - c.configObj.ClusterName = clusterName + c.configObj.ClusterName = adoptClusterName(clusterName) } func (c *ClusterConfig) GetClusterName() string { return c.configObj.ClusterName @@ -472,3 +472,7 @@ func DeleteConfigMap(k8s *k8sinterface.KubernetesApi) error { func DeleteConfigFile() error { return os.Remove(ConfigFileFullPath()) } + +func adoptClusterName(clusterName string) string { + return strings.ReplaceAll(clusterName, "/", "-") +} diff --git a/cautils/getter/loadpolicy.go b/cautils/getter/loadpolicy.go index 223ee7ec..e71451d5 100644 --- a/cautils/getter/loadpolicy.go +++ b/cautils/getter/loadpolicy.go @@ -17,12 +17,12 @@ const DefaultLocalStore = ".kubescape" // Load policies from a local repository type LoadPolicy struct { - filePath string + filePaths []string } -func NewLoadPolicy(filePath string) *LoadPolicy { +func NewLoadPolicy(filePaths []string) *LoadPolicy { return &LoadPolicy{ - filePath: filePath, + filePaths: filePaths, } } @@ -30,7 +30,8 @@ func NewLoadPolicy(filePath string) *LoadPolicy { func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, error) { control := &reporthandling.Control{} - f, err := os.ReadFile(lp.filePath) + filePath := lp.getFileForControl() + f, err := os.ReadFile(filePath) if err != nil { return nil, err } @@ -38,35 +39,49 @@ func (lp *LoadPolicy) GetControl(controlName string) (*reporthandling.Control, e if err = json.Unmarshal(f, control); err != nil { return control, err } - if controlName != "" && !strings.EqualFold(controlName, control.Name) && !strings.EqualFold(controlName, control.ControlID) { - return nil, fmt.Errorf("control from file not matching") + framework, err := lp.GetFramework(controlName) + if err != nil { + return nil, fmt.Errorf("control from file not matching") + } else { + for _, ctrl := range framework.Controls { + if strings.EqualFold(ctrl.Name, controlName) || strings.EqualFold(ctrl.ControlID, controlName) { + control = &ctrl + break + } + } + } } return control, err } func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) { - framework := &reporthandling.Framework{} - f, err := os.ReadFile(lp.filePath) - if err != nil { - return nil, err - } + var err error + for _, filePath := range lp.filePaths { + f, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } - if err = json.Unmarshal(f, framework); err != nil { - return framework, err + if err = json.Unmarshal(f, framework); err != nil { + return framework, err + } + if strings.EqualFold(frameworkName, framework.Name) { + break + } } - if frameworkName != "" && !strings.EqualFold(frameworkName, framework.Name) { + return nil, fmt.Errorf("framework from file not matching") } return framework, err } func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotypes.PostureExceptionPolicy, error) { - + filePath := lp.getFileForException() exception := []armotypes.PostureExceptionPolicy{} - f, err := os.ReadFile(lp.filePath) + f, err := os.ReadFile(filePath) if err != nil { return nil, err } @@ -74,3 +89,11 @@ func (lp *LoadPolicy) GetExceptions(customerGUID, clusterName string) ([]armotyp err = json.Unmarshal(f, &exception) return exception, err } + +func (lp *LoadPolicy) getFileForException() string { + return lp.filePaths[0] +} + +func (lp *LoadPolicy) getFileForControl() string { + return lp.filePaths[0] +} diff --git a/cautils/scaninfo.go b/cautils/scaninfo.go index 305b400c..ca0b064d 100644 --- a/cautils/scaninfo.go +++ b/cautils/scaninfo.go @@ -9,9 +9,9 @@ import ( type ScanInfo struct { Getters - PolicyIdentifier reporthandling.PolicyIdentifier + PolicyIdentifier []reporthandling.PolicyIdentifier UseExceptions string // Load exceptions configuration - UseFrom string // Load framework from local file (instead of download). Use when running offline + UseFrom []string // Load framework from local file (instead of download). Use when running offline UseDefault bool // Load framework from cached file (instead of download). Use when running offline Format string // Format results (table, json, junit ...) Output string // Store results in an output file, Output file name @@ -41,22 +41,21 @@ func (scanInfo *ScanInfo) Init() { func (scanInfo *ScanInfo) setUseExceptions() { if scanInfo.UseExceptions != "" { // load exceptions from file - scanInfo.ExceptionsGetter = getter.NewLoadPolicy(scanInfo.UseExceptions) + scanInfo.ExceptionsGetter = getter.NewLoadPolicy([]string{scanInfo.UseExceptions}) } else { scanInfo.ExceptionsGetter = getter.GetArmoAPIConnector() } } func (scanInfo *ScanInfo) setUseFrom() { - if scanInfo.UseFrom != "" { - return - } if scanInfo.UseDefault { - scanInfo.UseFrom = getter.GetDefaultPath(scanInfo.PolicyIdentifier.Name + ".json") + for _, policy := range scanInfo.PolicyIdentifier { + scanInfo.UseFrom = append(scanInfo.UseFrom, getter.GetDefaultPath(policy.Name+".json")) + } } } func (scanInfo *ScanInfo) setGetter() { - if scanInfo.UseFrom != "" { + if len(scanInfo.UseFrom) > 0 { // load from file scanInfo.PolicyGetter = getter.NewLoadPolicy(scanInfo.UseFrom) } else { diff --git a/clihandler/cmd/control.go b/clihandler/cmd/control.go index e89ba905..18a18853 100644 --- a/clihandler/cmd/control.go +++ b/clihandler/cmd/control.go @@ -13,22 +13,40 @@ import ( // controlCmd represents the control command var controlCmd = &cobra.Command{ - Use: "control /", + Use: "control /.\nExamples:\n$ kubescape scan control C-0058,C-0057 [flags]\n$ kubescape scan contol C-0058 [flags]\n$ kubescape scan control 'privileged container,allowed hostpath' [flags]", Short: fmt.Sprintf("The control you wish to use for scan. It must be present in at least one of the folloiwng frameworks: %s", clihandler.ValidFrameworks), Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) { - return fmt.Errorf("requires at least one argument") + if len(args) > 0 { + controls := strings.Split(args[0], ",") + if len(controls) > 1 { + if controls[1] == "" { + return fmt.Errorf("usage: ,") + } + } + } else { + return fmt.Errorf("requires at least one control name") } return nil }, RunE: func(cmd *cobra.Command, args []string) error { flagValidationControl() - scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{} - if !(cmd.Flags().Lookup("use-from").Changed) { - scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0]) + scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{} + + if len(args) < 1 { + scanInfo.PolicyIdentifier = SetScanForGivenFrameworks(clihandler.SupportedFrameworks) + } else { + var controls []string + if len(args) > 0 { + controls = strings.Split(args[0], ",") + scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{} + scanInfo.PolicyIdentifier = setScanForFirstControl(controls) + } + + if len(controls) > 1 { + scanInfo.PolicyIdentifier = SetScanForGivenControls(controls[1:]) + } } scanInfo.FrameworkScan = false - scanInfo.PolicyIdentifier.Kind = reporthandling.KindControl scanInfo.Init() cautils.SetSilentMode(scanInfo.Silent) err := clihandler.CliSetup(&scanInfo) @@ -51,3 +69,22 @@ func flagValidationControl() { os.Exit(1) } } + +func setScanForFirstControl(controls []string) []reporthandling.PolicyIdentifier { + newPolicy := reporthandling.PolicyIdentifier{} + newPolicy.Kind = reporthandling.KindControl + newPolicy.Name = controls[0] + scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy) + return scanInfo.PolicyIdentifier +} + +func SetScanForGivenControls(controls []string) []reporthandling.PolicyIdentifier { + for _, control := range controls { + control := strings.TrimLeft(control, " ") + newPolicy := reporthandling.PolicyIdentifier{} + newPolicy.Kind = reporthandling.KindControl + newPolicy.Name = control + scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy) + } + return scanInfo.PolicyIdentifier +} diff --git a/clihandler/cmd/framework.go b/clihandler/cmd/framework.go index 9461dce5..9b75e224 100644 --- a/clihandler/cmd/framework.go +++ b/clihandler/cmd/framework.go @@ -9,32 +9,42 @@ import ( "github.com/armosec/kubescape/cautils" "github.com/armosec/kubescape/clihandler" "github.com/armosec/opa-utils/reporthandling" - "github.com/spf13/cobra" ) var frameworkCmd = &cobra.Command{ - - Use: fmt.Sprintf("framework [``/`-`] [flags]\nSupported frameworks: %s", clihandler.ValidFrameworks), + Use: fmt.Sprintf("framework [``/`-`] [flags]\nExamples:\n$ kubescape scan framework nsa [flags]\n$ kubescape scan framework mitre,nsa [flags]\n$ kubescape scan framework 'nsa, mitre' [flags]\nSupported frameworks: %s", clihandler.ValidFrameworks), Short: fmt.Sprintf("The framework you wish to use. Supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", ")), Long: "Execute a scan on a running Kubernetes cluster or `yaml`/`json` files (use glob) or `-` for stdin", ValidArgs: clihandler.SupportedFrameworks, Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 && !(cmd.Flags().Lookup("use-from").Changed) { - return fmt.Errorf("requires at least one argument") - } else if len(args) > 0 { - if !isValidFramework(strings.ToLower(args[0])) { - return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", "))) + if len(args) > 0 { + // "nsa, mitre" -> ["nsa", "mitre"] and nsa,mitre -> ["nsa", "mitre"] + frameworks := strings.Split(strings.Join(strings.Fields(args[0]), ""), ",") + for _, framework := range frameworks { + if !isValidFramework(strings.ToLower(framework)) { + return fmt.Errorf(fmt.Sprintf("supported frameworks: %s", strings.Join(clihandler.SupportedFrameworks, ", "))) + } } + } else { + return fmt.Errorf("requires at least one framework name") } return nil }, RunE: func(cmd *cobra.Command, args []string) error { - scanInfo.PolicyIdentifier = reporthandling.PolicyIdentifier{} - scanInfo.PolicyIdentifier.Kind = reporthandling.KindFramework flagValidationFramework() - if !(cmd.Flags().Lookup("use-from").Changed) { - scanInfo.PolicyIdentifier.Name = strings.ToLower(args[0]) + scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{} + // If no framework provided, use all + if len(args) < 1 { + scanInfo.PolicyIdentifier = SetScanForGivenFrameworks(clihandler.SupportedFrameworks) + } else { + // Read frameworks from input args + scanInfo.PolicyIdentifier = []reporthandling.PolicyIdentifier{} + frameworks := strings.Split(strings.Join(strings.Fields(args[0]), ""), ",") + scanInfo.PolicyIdentifier = SetScanForFirstFramework(frameworks) + if len(frameworks) > 1 { + scanInfo.PolicyIdentifier = SetScanForGivenFrameworks(frameworks[1:]) + } } if len(args) > 0 { if len(args[1:]) == 0 || args[1] != "-" { @@ -74,11 +84,26 @@ func init() { frameworkCmd.Flags().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") frameworkCmd.Flags().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") frameworkCmd.Flags().StringVarP(&scanInfo.Account, "account", "", "", "Armo portal account ID. Default will load account ID from configMap or config file") +} +func SetScanForGivenFrameworks(frameworks []string) []reporthandling.PolicyIdentifier { + for _, framework := range frameworks { + newPolicy := reporthandling.PolicyIdentifier{} + newPolicy.Kind = reporthandling.KindFramework + newPolicy.Name = framework + scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy) + } + return scanInfo.PolicyIdentifier +} +func SetScanForFirstFramework(frameworks []string) []reporthandling.PolicyIdentifier { + newPolicy := reporthandling.PolicyIdentifier{} + newPolicy.Kind = reporthandling.KindFramework + newPolicy.Name = frameworks[0] + scanInfo.PolicyIdentifier = append(scanInfo.PolicyIdentifier, newPolicy) + return scanInfo.PolicyIdentifier } func flagValidationFramework() { - if scanInfo.Submit && scanInfo.Local { fmt.Println("You can use `keep-local` or `submit`, but not both") os.Exit(1) diff --git a/clihandler/cmd/scan.go b/clihandler/cmd/scan.go index e86e5a02..f9c2e65b 100644 --- a/clihandler/cmd/scan.go +++ b/clihandler/cmd/scan.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/clihandler" "github.com/spf13/cobra" ) @@ -16,15 +17,19 @@ var scanCmd = &cobra.Command{ Short: "Scan the current running cluster or yaml files", Long: `The action you want to perform`, Args: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return fmt.Errorf("requires one argument: framework/control") - } - if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") { - return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0]) + if len(args) > 0 { + if !strings.EqualFold(args[0], "framework") && !strings.EqualFold(args[0], "control") { + return fmt.Errorf("invalid parameter '%s'. Supported parameters: framework, control", args[0]) + } } return nil }, Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + frameworkArgs := []string{clihandler.ValidFrameworks} + frameworkArgs = append(frameworkArgs, args...) + frameworkCmd.RunE(cmd, frameworkArgs) + } }, } @@ -35,7 +40,7 @@ func init() { scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout") scanCmd.PersistentFlags().BoolVarP(&scanInfo.Silent, "silent", "s", false, "Silent progress messages") scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 0, "Failure threshold is the percent bellow which the command fails and returns exit code 1") - scanCmd.PersistentFlags().StringVar(&scanInfo.UseFrom, "use-from", "", "Load local framework object from specified path. If not used will download latest") - scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local framework object from default path. If not used will download latest") + scanCmd.PersistentFlags().StringSliceVar(&scanInfo.UseFrom, "use-from", nil, "Load local policy object from specified path. If not used will download latest") + scanCmd.PersistentFlags().BoolVar(&scanInfo.UseDefault, "use-default", false, "Load local policy object from default path. If not used will download latest") scanCmd.PersistentFlags().StringVar(&scanInfo.UseExceptions, "exceptions", "", "Path to an exceptions obj. If not set will download exceptions from Armo management portal") } diff --git a/clihandler/cmd/version.go b/clihandler/cmd/version.go index 45ba5bc2..f4623813 100644 --- a/clihandler/cmd/version.go +++ b/clihandler/cmd/version.go @@ -29,7 +29,7 @@ func GetLatestVersion() (string, error) { } defer resp.Body.Close() if resp.StatusCode < 200 || 301 < resp.StatusCode { - return "unknown", fmt.Errorf("failed to download file, status code: %s", resp.Status) + return "unknown", nil } body, err := io.ReadAll(resp.Body) diff --git a/clihandler/initcli.go b/clihandler/initcli.go index 1fdaae44..a502cc74 100644 --- a/clihandler/initcli.go +++ b/clihandler/initcli.go @@ -135,10 +135,8 @@ func (clihandler *CLIHandler) Scan() error { cautils.ScanStartDisplay() policyNotification := &reporthandling.PolicyNotification{ NotificationType: reporthandling.TypeExecPostureScan, - Rules: []reporthandling.PolicyIdentifier{ - clihandler.scanInfo.PolicyIdentifier, - }, - Designators: armotypes.PortalDesignator{}, + Rules: clihandler.scanInfo.PolicyIdentifier, + Designators: armotypes.PortalDesignator{}, } switch policyNotification.NotificationType { case reporthandling.TypeExecPostureScan: diff --git a/main.go b/main.go index 956b6622..84329f92 100644 --- a/main.go +++ b/main.go @@ -5,19 +5,25 @@ import ( "os" "github.com/armosec/kubescape/clihandler/cmd" + pkgutils "github.com/armosec/utils-go/utils" ) +const SKIP_VERSION_CHECK = "KUBESCAPE_SKIP_UPDATE_CHECK" + func main() { CheckLatestVersion() cmd.Execute() } func CheckLatestVersion() { + if v, ok := os.LookupEnv(SKIP_VERSION_CHECK); ok && pkgutils.StringToBool(v) { + return + } latest, err := cmd.GetLatestVersion() - if err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) - } else if latest != cmd.BuildNumber { + if err != nil || latest == "unknown" { + return + } + if latest != cmd.BuildNumber { fmt.Println("Warning: You are not updated to the latest release: " + latest) } - } diff --git a/policyhandler/handlepullpolicies.go b/policyhandler/handlepullpolicies.go index 0762a05e..579c05db 100644 --- a/policyhandler/handlepullpolicies.go +++ b/policyhandler/handlepullpolicies.go @@ -14,9 +14,11 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporth frameworks := []reporthandling.Framework{} exceptionPolicies := []armotypes.PostureExceptionPolicy{} // Get - cacli opa get - for _, rule := range notification.Rules { - switch rule.Kind { - case reporthandling.KindFramework: + rule := GetScanKind(notification) + + switch rule.Kind { + case reporthandling.KindFramework: + for _, rule := range notification.Rules { receivedFramework, recExceptionPolicies, err := policyHandler.getFrameworkPolicies(rule.Name) if receivedFramework != nil { frameworks = append(frameworks, *receivedFramework) @@ -29,27 +31,32 @@ func (policyHandler *PolicyHandler) GetPoliciesFromBackend(notification *reporth } return nil, nil, fmt.Errorf("kind: %v, name: %s, error: %s", rule.Kind, rule.Name, err.Error()) } - case reporthandling.KindControl: - receivedControls, recExceptionPolicies, err := policyHandler.getControl(rule.Name) - if receivedControls != nil { - f := reporthandling.Framework{ - Controls: receivedControls, - } - frameworks = append(frameworks, f) + } + case reporthandling.KindControl: + f := reporthandling.Framework{} + var receivedControl *reporthandling.Control + var recExceptionPolicies []armotypes.PostureExceptionPolicy + var err error + for _, rule := range notification.Rules { + receivedControl, recExceptionPolicies, err = policyHandler.getControl(rule.Name) + if receivedControl != nil { + f.Controls = append(f.Controls, *receivedControl) if recExceptionPolicies != nil { exceptionPolicies = append(exceptionPolicies, recExceptionPolicies...) } + } else if err != nil { if strings.Contains(err.Error(), "unsupported protocol scheme") { err = fmt.Errorf("failed to download from GitHub release, try running with `--use-default` flag") } return nil, nil, fmt.Errorf("error: %s", err.Error()) } - // TODO: add case for control from file - default: - err := fmt.Errorf("missing rule kind, expected: %s", reporthandling.KindFramework) - errs = fmt.Errorf("%s", err.Error()) } + frameworks = append(frameworks, f) + // TODO: add case for control from file + default: + err := fmt.Errorf("missing rule kind, expected: %s", reporthandling.KindFramework) + errs = fmt.Errorf("%s", err.Error()) } return frameworks, exceptionPolicies, errs } @@ -68,24 +75,30 @@ func (policyHandler *PolicyHandler) getFrameworkPolicies(policyName string) (*re return receivedFramework, receivedException, nil } +func GetScanKind(notification *reporthandling.PolicyNotification) *reporthandling.PolicyIdentifier { + if len(notification.Rules) > 0 { + return ¬ification.Rules[0] + } + return nil +} + // Get control by name -func (policyHandler *PolicyHandler) getControl(policyName string) ([]reporthandling.Control, []armotypes.PostureExceptionPolicy, error) { +func (policyHandler *PolicyHandler) getControl(policyName string) (*reporthandling.Control, []armotypes.PostureExceptionPolicy, error) { - controls := []reporthandling.Control{} - - control, err := policyHandler.getters.PolicyGetter.GetControl(policyName) + control := &reporthandling.Control{} + var err error + control, err = policyHandler.getters.PolicyGetter.GetControl(policyName) if err != nil { - return nil, nil, err + return control, nil, err } - if control == nil { - return nil, nil, fmt.Errorf("control not found") - } - controls = append(controls, *control) + // if control == nil { + // return control, nil, fmt.Errorf("control not found") + // } exceptions, err := policyHandler.getters.ExceptionsGetter.GetExceptions(cautils.CustomerGUID, cautils.ClusterName) if err != nil { - return controls, nil, err + return control, nil, err } - return controls, exceptions, nil + return control, exceptions, nil } diff --git a/resultshandling/printer/jsonprinter.go b/resultshandling/printer/jsonprinter.go index ff80feb8..01acad7e 100644 --- a/resultshandling/printer/jsonprinter.go +++ b/resultshandling/printer/jsonprinter.go @@ -25,7 +25,15 @@ func (jsonPrinter *JsonPrinter) Score(score float32) { } func (jsonPrinter *JsonPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { - postureReportStr, err := json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0]) + var postureReportStr []byte + var err error + + if len(opaSessionObj.PostureReport.FrameworkReports) == 1 { + postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports[0]) + } else { + postureReportStr, err = json.Marshal(opaSessionObj.PostureReport.FrameworkReports) + } + if err != nil { fmt.Println("Failed to convert posture report object!") os.Exit(1) diff --git a/resultshandling/printer/prettyprinter.go b/resultshandling/printer/prettyprinter.go index 1d4b366b..225b6844 100644 --- a/resultshandling/printer/prettyprinter.go +++ b/resultshandling/printer/prettyprinter.go @@ -24,12 +24,25 @@ func NewPrettyPrinter() *PrettyPrinter { } } +// Initializes empty printer for new table +func (printer *PrettyPrinter) init() *PrettyPrinter { + printer.frameworkSummary = ControlSummary{} + printer.summary = Summary{} + printer.sortedControlNames = []string{} + return printer +} + func (printer *PrettyPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { // score := calculatePostureScore(opaSessionObj.PostureReport) - - printer.summarySetup(opaSessionObj.PostureReport) - printer.printResults() - printer.printSummaryTable() + for _, report := range opaSessionObj.PostureReport.FrameworkReports { + // Print summary table together for control scan + if report.Name != "" { + printer = printer.init() + } + printer.summarySetup(report) + printer.printResults() + printer.printSummaryTable(report.Name) + } // return score } @@ -41,29 +54,27 @@ func (printer *PrettyPrinter) SetWriter(outputFile string) { func (printer *PrettyPrinter) Score(score float32) { } -func (printer *PrettyPrinter) summarySetup(postureReport *reporthandling.PostureReport) { - for _, fr := range postureReport.FrameworkReports { - printer.frameworkSummary = ControlSummary{ - TotalResources: fr.GetNumberOfResources(), - TotalFailed: fr.GetNumberOfFailedResources(), - TotalWarnign: fr.GetNumberOfWarningResources(), +func (printer *PrettyPrinter) summarySetup(fr reporthandling.FrameworkReport) { + printer.frameworkSummary = ControlSummary{ + TotalResources: fr.GetNumberOfResources(), + TotalFailed: fr.GetNumberOfFailedResources(), + TotalWarnign: fr.GetNumberOfWarningResources(), + } + for _, cr := range fr.ControlReports { + if len(cr.RuleReports) == 0 { + continue } - for _, cr := range fr.ControlReports { - if len(cr.RuleReports) == 0 { - continue - } - workloadsSummary := listResultSummary(cr.RuleReports) + workloadsSummary := listResultSummary(cr.RuleReports) - printer.summary[cr.Name] = ControlSummary{ - TotalResources: cr.GetNumberOfResources(), - TotalFailed: cr.GetNumberOfFailedResources(), - TotalWarnign: cr.GetNumberOfWarningResources(), - FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed), - ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude), - Description: cr.Description, - Remediation: cr.Remediation, - ListInputKinds: cr.ListControlsInputKinds(), - } + printer.summary[cr.Name] = ControlSummary{ + TotalResources: cr.GetNumberOfResources(), + TotalFailed: cr.GetNumberOfFailedResources(), + TotalWarnign: cr.GetNumberOfWarningResources(), + FailedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryFailed), + ExcludedWorkloads: groupByNamespace(workloadsSummary, workloadSummaryExclude), + Description: cr.Description, + Remediation: cr.Remediation, + ListInputKinds: cr.ListControlsInputKinds(), } } printer.sortedControlNames = printer.getSortedControlsNames() @@ -179,7 +190,10 @@ func generateFooter(numControlers, sumFailed, sumWarning, sumTotal int) []string } return row } -func (printer *PrettyPrinter) printSummaryTable() { +func (printer *PrettyPrinter) printSummaryTable(framework string) { + // For control scan framework will be nil + printer.printFramework(framework) + summaryTable := tablewriter.NewWriter(printer.writer) summaryTable.SetAutoWrapText(false) summaryTable.SetHeader(generateHeader()) @@ -195,6 +209,12 @@ func (printer *PrettyPrinter) printSummaryTable() { summaryTable.Render() } +func (printer *PrettyPrinter) printFramework(framework string) { + if framework != "" { + cautils.InfoTextDisplay(printer.writer, fmt.Sprintf("%s FRAMEWORK\n", framework)) + } +} + func (printer *PrettyPrinter) getSortedControlsNames() []string { controlNames := make([]string, 0, len(printer.summary)) for k := range printer.summary {